I have a PDF file saved in the document directory. The path to the file is stored in a NSString property 'self.URL'.
Here is my code to present the activity items:
-(void)presentDocumentInteractionController{
self.docController = [UIDocumentInteractionController interactionControllerWithURL:self.URL];
self.docController.delegate = self;
[_docController setUTI:#"com.adobe.pdf"];
[_docController presentOptionsMenuFromBarButtonItem:self.activityBarButton animated:YES];
}
Before iOS 8 this code worked fine. The user was presented with a list of items such as print, copy and email to choose from. After upgrading to iOS 8 /XCode 6, I'm getting this runtime error (it doesnt crash the app):
Unknown activity items supplied: (
"<UITextViewPrintFormatter: 0x7f908ba53ca0>"
)
How can I solve this problem?
I have the same problem and have switched to using UIActivityViewController. However this makes the apps capable of opening the PDF no longer show up, so maybe that's not what you want.
Minimal Solution:
If you want to do minimal work, you don't even need to read your PDF into NSData, use a NSURL as activity item and iOS seems to know what to do:
- (void)share:(id)sender
{
UIActivityViewController *activity =
[[UIActivityViewController alloc] initWithActivityItems:#[self.URL]
applicationActivities:nil];
if ([activity respondsToSelector:#selector(popoverPresentationController)]) {
activity.popoverPresentationController.barButtonItem = <# BAR BUTTON ITEM #>;
}
[self presentViewController:activity animated:YES completion:NULL];
}
Original Answer:
Make your view controller adhere to the UIActivityItemSource protocol, then you can do:
- (void)share:(id)sender
{
self.pdfData = [NSData dataWithContentsOfURL:self.URL];
UIActivityViewController *activity = [[UIActivityViewController alloc] initWithActivityItems:#[self] applicationActivities:nil];
if ([activity respondsToSelector:#selector(popoverPresentationController)]) {
activity.popoverPresentationController.barButtonItem = <# BAR BUTTON ITEM #>;
}
[self presentViewController:activity animated:YES completion:NULL];
}
Adhering to the protocol if you have a PDF file is relatively simple. You can of course optimize and return smaller NSData and even a preview image, but minimally do this:
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
{
return _pdfData;
}
- (id)activityViewController:(UIActivityViewController *)activityViewController
itemForActivityType:(NSString *)activityType
{
return _pdfData;
}
- (NSString *)activityViewController:(UIActivityViewController *)activityViewController
subjectForActivityType:(NSString *)activityType
{
return self.title;
}
- (NSString *)activityViewController:(UIActivityViewController *)activityViewController
dataTypeIdentifierForActivityType:(NSString *)activityType
{
return #"com.adobe.pdf";
}
Use
- (BOOL)presentOpenInMenuFromBarButtonItem:(UIBarButtonItem *)item animated:(BOOL)animated;
Instead of
- (BOOL)presentOptionsMenuFromBarButtonItem:(UIBarButtonItem *)item animated:(BOOL)animated;
This solved the problem for me:
ObjC:
dispatch_async(dispatch_get_main_queue(), ^() {
[_docController presentOptionsMenuFromRect:button.bounds inView:button animated:YES];
});
Swift:
if let docController = UIDocumentInteractionController(URL: url) {
dispatch_async(dispatch_get_main_queue()) {
docController.presentPreviewAnimated(true)
}
} else {
// handle nil docController
}
I don't use swift so this code might not work.
Related
I'm using Objective-C. I want to open a local file with quick look. And I have this code in my mainViewController:
- (IBAction)open:(id)sender {
QLPreviewController *myQlPreViewController = [[QLPreviewController alloc]init];
myQlPreViewController.delegate = self;
myQlPreViewController.dataSource = self;
[myQlPreViewController setCurrentPreviewItemIndex:0];
[self presentViewController:myQlPreViewController animated:YES completion:nil];
}
- (NSInteger)numberOfPreviewItemsInPreviewController:(QLPreviewController *)controller
{
return 1;
}
- (id<QLPreviewItem>)previewController:(QLPreviewController *)controller previewItemAtIndex:(NSInteger)index
{
return [NSURL URLWithString:self.filePath];
}
But it shows a gray background and file name on it. How can I fix it to show the file content?
Thanks in advance!
In fact, it's because quick look can't open that file. So, it can only show a file name on the screen.
i have created a simple app with two buttons and one uiimageview.
One button takes a photo from photo library and puts it on uiimageview.
When the user adds another photo the old one should be saved and then user can swipe between these two photos.
another button takes a photo and put it on uiimageview.
So now i'm really confused about swiping between photos. I read that uiimageview can contain only one image. Also, i read that i need to add uiScrollView to UiImageView, but i don't know how they work together.
should i delete my UIImageView and then create ScrollView instead of it?
Here is my code
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (IBAction)pickPhoto:(id)sender {
picker1 = [[UIImagePickerController alloc]init];
picker1.delegate = self;
[picker1 setSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
[self presentViewController:picker1 animated:YES completion:NULL];
}
- (IBAction)takePhoto:(id)sender {
picker2 = [[UIImagePickerController alloc]init];
picker2.delegate = self;
if([UIImagePickerController isSourceTypeAvailable: UIImagePickerControllerSourceTypeCamera]){
}
else{
NSLog(#"not avaialbe");
}
[self presentViewController:picker2 animated:YES completion:NULL];
}
- (void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
image = [info objectForKey:UIImagePickerControllerOriginalImage];
[self.imageView setImage:image];
[self dismissViewControllerAnimated:YES completion:NULL];
}
- (void) imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
[self dismissViewControllerAnimated:YES completion:NULL];
}
UPDATE
So i have added a method that allows user to swipe left or right and he can see different pictures, however, i had to hard code my images into the code. But i want to select images from the photo library, then these images will be saved in the array and then if user adds more pictures he has a choice to swipe through his old images.
Here is my code for swiping through the images
- (IBAction)Swipe:(id)sender {
NSArray *images = [[NSArray alloc]initWithObjects:#"ayat.png", #"qwe.png",#"4444", #"15", nil];
UISwipeGestureRecognizerDirection direction = [(UISwipeGestureRecognizer *) sender direction];
switch(direction)
{
case UISwipeGestureRecognizerDirectionLeft:
imageIndex++;
break;
case UISwipeGestureRecognizerDirectionRight:
imageIndex--;
break;
default:
break;
}
imageIndex = (imageIndex <0)?([images count] -1 ):
imageIndex % [images count];
self.imageView.image = [UIImage imageNamed:[images objectAtIndex:imageIndex]];
}
but here i added pictures to my project.
It's true that a UIImageView can display only a single image.
There are various ways to handle this. One would be to use a UIPageViewController. That would enable you to set up side-swipe based paging between pages of content. There is a sample app from Apple called PhotoScroller (it used to be linked from the Xcode help system. With Xcode 6, apple seems to have removed all sample code. You'll have to search the net for the sample app. That app also includes tiled rendering, which is likely overkill for your application, but it does show how to set up a page view controller.
You could also use a UICollectionView, or create your own custom view that manages a set of views.
Actually now that I think about it a UICollectionView set up for paging might be your best bet.
I have simple singleton class that takes care of creating and presenting UIImagePickerController. It worked just fine until few hours ago. All I did was moving the code from ViewController to separate singleton class (relevant code at the end of post). When I tried to undo all changes to get back to previous state, the problem persisted.
Whenever I present camera, I get warning (not sure if it is related or not)
"Snapshotting a view that has not been rendered results in an empty snapshot. Ensure your view has been rendered at least once before snapshotting or snapshot after screen updates."
Then, when I hit shutter button, nothing happens, no sound, no focus, no photo taken, no callback back to my app.
When I hit cancel, I get the callback and dismiss the controller without problems.
Now when I open camera again, the screen practically freezes, I get the controls and everything, but the screen doesn't update (actually it does, but only once or twice a minute)
#interface AddPhotoManager()
#property (nonatomic, weak) UIViewController *controller;
#property (nonatomic, strong) UIImagePickerController *imagePickerController;
#end
#implementation AddPhotoManager
+ (instancetype)sharedInstance
{
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (void)addNewPhotoFromController:(UIViewController*)controller
{
self.controller = controller;
UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:#"Cancel" destructiveButtonTitle:nil otherButtonTitles:#"Camera", #"Photo library", nil];
[sheet showInView:self.controller.view];
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if(buttonIndex == 0 || buttonIndex == 1) {
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
// Will get here on both iOS 7 & 8 even though camera permissions weren't required
// until iOS 8. So for iOS 7 permission will always be granted.
if (granted) {
// Permission has been granted. Use dispatch_async for any UI updating
// code because this block may be executed in a thread.
dispatch_async(dispatch_get_main_queue(), ^{
[self presentPickerController:buttonIndex == 0];
});
} else {
// Permission has been denied.
}
}];
}
}
- (void)presentPickerController:(bool)camera
{
self.imagePickerController = [[UIImagePickerController alloc] init];
self.imagePickerController.sourceType = camera ? UIImagePickerControllerSourceTypeCamera : UIImagePickerControllerSourceTypeSavedPhotosAlbum;
self.imagePickerController.delegate = self;
[self.controller presentViewController:self.imagePickerController animated:false completion:nil];
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
[picker dismissViewControllerAnimated:true completion:nil];
UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
UIImageWriteToSavedPhotosAlbum(image, self, nil, nil);
// TODO: upload the image
self.imagePickerController = nil;
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
[picker dismissViewControllerAnimated:true completion:nil];
self.imagePickerController = nil;
}
The singleton is used simply by calling
[[AddPhotoManager sharedInstance] addNewPhotoFromController:self];
from any view controller
EDIT:
storing imagePickerController in strong property doesn't seem to be necessary, but I wasn't sure so I tried it. However, results are same in both cases.
EDIT 2:
I tried exact same code in new test app and everything worked without problems, so it must be some interference with some other parts of the app? Any ideas what it could be? I tried deleting the app to reset permissions, even changed bundle Id, but nothing helped
Update 2, I hope this helps someone, there is a solutions at the following link: https://discussions.apple.com/thread/5498630?start=0&tstart=0 , evidently this is an iOS bug and this work around works. I can create the new sharedPicker, but I cannot get anything from it or dismiss it, I'm not sure how to format beyond what is supplied at the link
Any help on that is very welcome.
So my question now is how to take the following code and actually create the code for peoplePickerNavigationControllerDidCancel: and peoplePickerNavigationController:shouldContinueAfterSelectingPerson:
Thanks. I've left most of my original post in case someone has a similar vague issue.
// Convoluted workaround for the iPhone 4S crash
+ (ABPeoplePickerNavigationController *)sharedPeoplePicker {
static ABPeoplePickerNavigationController *_sharedPicker = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedPicker = [[ABPeoplePickerNavigationController alloc] init];
});
return _sharedPicker;
}
// then later on, use
[YourController sharedPeoplePicker].delegate = self;
// etc.
My current code:
- (BOOL)peoplePickerNavigationController:
(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property
identifier:(ABMultiValueIdentifier)identifier
{
[self displayPerson:person];
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
return NO;
}
- (void)peoplePickerNavigationControllerDidCancel:
(ABPeoplePickerNavigationController *)peoplePicker
{
//[self dismissViewControllerAnimated:YES completion:nil];
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
}
- (IBAction)pick1:(id)sender
{
ABPeoplePickerNavigationController *picker1 =[[ABPeoplePickerNavigationController alloc] init];
picker1.peoplePickerDelegate = self;
[self presentViewController:picker1 animated:YES completion:nil];
x=1;
}
Update 1, this app crashes on iPhone 4/4s, but runs in the simulator and iPhone5 if that means anything. I'm thinking its just they have enough power to get past whatever leak I have created
I have an iOS app with a view controller where the user can chose contacts for the app using ABPeoplePickerNavigationController or enter numbers manually. If numbers are entered manually there are no issues. If the user opens and:
Picks a new contact from the address book
Updates a contact from the address book to use in the app
Opens and cancels the address book (all without saving the action)
Then I cannot navigate to one particular view in my app without a crash. I am at a loss why I cannot go to this one view controller, or why it causes the crash.
I am using 5 different pickers, one for each contact I want to add and potentially save. I save as NSUserDefaults, but like I said, the crash persists even if the picker selection is never saved. I can navigate to all view in the app from a sidebar navigation without incident, the only thing different about the view I fail on is that it is presented from one of the main view controllers and not my sidebar.
I appreciate any help or thoughts. This was the first app I wrote and I'm trying to update it and failing. I want to get it functional again so I can come back and refactor it.
My implementation:
- (IBAction)pick1:(id)sender
{
ABPeoplePickerNavigationController *picker1 =
[[ABPeoplePickerNavigationController alloc] init];
picker1.peoplePickerDelegate = self;
[self presentViewController:picker1 animated:YES completion:nil];
x = 1;
}
- (IBAction)pick2:(id)sender
{
ABPeoplePickerNavigationController *picker2 =
[[ABPeoplePickerNavigationController alloc] init];
picker2.peoplePickerDelegate = self;
[self presentViewController:picker2 animated:YES completion:nil];
x=2;
}
- (IBAction)pick3:(id)sender
{
ABPeoplePickerNavigationController *picker3 =
[[ABPeoplePickerNavigationController alloc] init];
picker3.peoplePickerDelegate = self;
[self presentViewController:picker3 animated:YES completion:nil];
x=3;
}
- (IBAction)pick4:(id)sender
{
ABPeoplePickerNavigationController *picker4 =
[[ABPeoplePickerNavigationController alloc] init];
picker4.peoplePickerDelegate = self;
[self presentViewController:picker4 animated:YES completion:nil];
x=4;
}
- (IBAction)pick5:(id)sender
{
ABPeoplePickerNavigationController *picker5 =
[[ABPeoplePickerNavigationController alloc] init];
picker5.peoplePickerDelegate = self;
[self presentViewController:picker5 animated:YES completion:nil];
x=5;
}
- (void)peoplePickerNavigationControllerDidCancel:
(ABPeoplePickerNavigationController *)peoplePicker
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (BOOL)peoplePickerNavigationController:
(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person {
[self displayPerson:person];
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
return NO;
}
- (BOOL)peoplePickerNavigationController:
(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property
identifier:(ABMultiValueIdentifier)identifier
{
return NO;
}
- (void)displayPerson:(ABRecordRef)person
{
NSString* name = (__bridge_transfer NSString*)ABRecordCopyValue(person,
kABPersonFirstNameProperty);
NSString* phone = nil;
ABMultiValueRef phoneNumbers = ABRecordCopyValue(person,
kABPersonPhoneProperty);
if (ABMultiValueGetCount(phoneNumbers) > 0) {
phone = (__bridge_transfer NSString*)
ABMultiValueCopyValueAtIndex(phoneNumbers, 0);
} else {
phone = #"[None]";
}
if (x==1){
firstName1.text = name;
contact1.text = phone;
}
if (x==2){
firstName2.text = name;
contact2.text = phone;
}
if (x==3){
firstName3.text = name;
contact3.text = phone;
}
if (x==4){
firstName4.text = name;
contact4.text = phone;
}
if (x==5){
firstName5.text = name;
contact5.text = phone;
}
}
Sorry to answer my own question, but another work around to this that requires little change is to use CF Retain to correct the over release I was experiencing. I retained the person and the peoplePicker and all was resolved. Thanks for everyone who tried to help me solve this.
- (void)peoplePickerNavigationControllerDidCancel:
(ABPeoplePickerNavigationController *)peoplePicker
{
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
CFRetain((__bridge CFTypeRef)(peoplePicker));
}
- (BOOL)peoplePickerNavigationController:
(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property
identifier:(ABMultiValueIdentifier)identifier
{
[self displayPerson:person];
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
CFRetain(person);
CFRetain((__bridge CFTypeRef)(peoplePicker));
return NO;
}
Thank You for the work around this issue. Your method #2 really works!!!
I have the similar situation discussed in https://discussions.apple.com/thread/5498630?start=0&tstart=0 : MKMapView is placed by IB in storyboard, no code except IBOutlet. I present ABPeoplePickerController then simply cancel it by dismiss and leave this view by navigation. Then return back and get memory issue: called method barStyle of zombie UINavigationBar at address of dismissed ABPeoplePickerController. This situation happened only on iOS 7 (iPad 3rd ten and iPhone 4S) but code works fine on iOS 6 (iPhone 3GS) and only with combination with MKMapView. I test with WebView instead of MapView and all works fine. I think it is real bug in iOS 7.
Kind regards
Alexey
Well, there is also a bit more simple solution to this. The actual problem is in using ABPeoplePickerNavigationController as a singleton object, setting its delegate to a view controller and then dismissing the view controller. So, in my case the solution that worked is this one:
- (void)peoplePickerNavigationControllerDidCancel:
(ABPeoplePickerNavigationController *)peoplePicker
{
peoplePicker.peoplePickerDelegate = nil; // clear delegate prior to dismissing self
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
}
- (BOOL)peoplePickerNavigationController:
(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property
identifier:(ABMultiValueIdentifier)identifier
{
[self displayPerson:person];
peoplePicker.peoplePickerDelegate = nil; // clear delegate prior to dismissing self
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
return NO;
}
I am using UIActivityViewController to present sharing options and I want to display another view controller when the UIActivityViewController is dismissed by the user or when the animation that follows "activityDidFinish:(bool)completed" gets over. When I try to present the other controller in the completion handler of the UIActivityViewController, I get the following warning and the second VC does not get displayed at all!
Attempt to present <_UIViewController: 0x1e16f020> on <###> while a presentation is in progress!
UIActivityViewController activityVC = [[UIActivityViewController alloc]initWithActivityItems:selectedAssetsURL applicationActivities:nil];
[activityVC setCompletionHandler:^(NSString *activityType, BOOL completed) {
NSLog(#"completed");
//Present another VC
}];
The question is, how to know when the UIActivityViewController disappears from the screen? Even the -ViewDidAppear method of the view controller which presents the UIActivityViewController does not get fired!
In this link there's good information about how the UIActivityViewController works:
http://www.apeth.com/iOSBook/ch26.html#_activity_view
Basically you can subclass the UIActivityViewController into another class and implement a method to know when it has been dissmissed.
UIActivityViewController* avc =
[[UIActivityViewController alloc]
initWithActivityItems:#[myCoolString]
applicationActivities:#[[MyCoolActivity new]]];
Here’s the preparatory part of the implementation of MyCoolActivity:
-(NSString *)activityType {
return #"com.neuburg.matt.coolActivity"; // make up your own unique string
}
-(NSString *)activityTitle {
return #"Be Cool";
}
-(UIImage *)activityImage {
return self.image; // prepared beforehand
}
-(BOOL)canPerformWithActivityItems:(NSArray *)activityItems {
for (id obj in activityItems) {
if ([obj isKindOfClass: [NSString class]])
return YES;
}
return NO;
}
-(void)prepareWithActivityItems:(NSArray *)activityItems {
self.items = activityItems;
}
To perform the activity, we implement one of two methods:
-(void)performActivity {
// ... do something with self.items here ...
[self activityDidFinish:YES];
}
-(UIViewController *)activityViewController {
MustacheViewController* mvc = [MustacheViewController new];
mvc.activity = self;
mvc.items = self.items;
return mvc;
}
And then MustacheViewController would have code like this:
- (IBAction)doCancel:(id)sender {
[self.activity activityDidFinish:NO];
}
- (IBAction)doDone:(id)sender {
[self.activity activityDidFinish:YES];
}