uitextviewuimenucontrollernstextattachment

How can I add a menu option to the NSTextAttachment popup menu is UITextView?


I want to add another menu option to the default image attachment menu options (Copy Image, Save to Camera Roll). Note that these options are shown when you long press on an image embedded in the UITextView if the textView is not in editing mode.

I have tried adding a custom menu to the uimenucontroller and using -(void)canPerformAction to enable or disable the option, however this seems to add the menu item to the uitextView's edit menu and has no affect on the attachments popup menu.

-(void)canPerformAction never seems to get called when long pressing on the image attachment.


Solution

  • Well according to Apple there is no public API for doing this, however as it turns out its relatively straight forward to replace the default menu with one that looks and behaves the same.

    In the viewController that contains the UITextView add the following or similar and set it up as the textView's delegate.

    - (BOOL)textView:(UITextView *)textView shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment inRange:(NSRange)characterRange {
    
        // save in ivar so we can access once action sheet option is selected
        _attachment = textAttachment;
    
        [self attachmentActionSheet:(UITextView *)textView range:characterRange];
    
        return NO;
    }
    - (void)attachmentActionSheet:(UITextView *)textView range:(NSRange)range {
    
        // get the rect for the selected attachment (if its a big image with top not visible the action sheet
        // will be positioned above the top limit of the UITextView
        // Need to add code to adjust for this.
        CGRect attachmentRect = [self frameOfTextRange:range inTextView:textView];
          _attachmentMenuSheet = [[UIActionSheet alloc] initWithTitle:nil
                                                               delegate:self
                                                      cancelButtonTitle:@"Cancel"
                                                 destructiveButtonTitle:nil
                                                      otherButtonTitles:@"Copy Image", @"Save to Camera Roll", @"Open in Viewer", nil];
    
        // Show the sheet
        [_attachmentMenuSheet showFromRect:attachmentRect inView:textView animated:YES];
    }
    - (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView {
    
        CGRect rect = [textView.layoutManager boundingRectForGlyphRange:range inTextContainer:textView.textContainer];
    
        // Now convert to textView coordinates
        CGRect rectRange = [textView convertRect:rect fromView:textView.textInputView];
    
        // Now convert to contentView coordinates
        CGRect rectRangeSuper = [self.contentView convertRect:rectRange fromView:textView];
    
        // Get the textView frame
        CGRect rectView = textView.frame;
    
        // Find the intersection of the two (in the same coordinate space)
        CGRect rectIntersect = CGRectIntersection(rectRangeSuper, rectView);
    
        // If no intersection then that's weird !!
        if (CGRectIsNull(rectIntersect)) {
            return rectRange;
        }
    
        // Now convert the intersection rect back to textView coordinates
        CGRect rectRangeInt = [textView convertRect:rectIntersect fromView:self.contentView];
    
        return rectRangeInt;
    }
    
    - (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
        if (actionSheet == _attachmentMenuSheet) {
            switch (buttonIndex) {
    
                case 0:
                    [self copyImageToPasteBoard:[_attachment image]];
                    break;
    
                case 1:
                    [self saveToCameraRoll:[_attachment image]];
                    break;
    
                case 2:
                    [self browseImage:[_attachment image]];
                    break;
    
                default:
                    break;
            }
        }
    }
    - (void)saveToCameraRoll:(UIImage*)image {
        UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
    }
    - (void)copyImageToPasteBoard:(UIImage*)image {
        UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
        NSData *data = UIImagePNGRepresentation(image);
        [pasteboard setData:data forPasteboardType:@"public.png"];
    }
    
    -(void)browseImage:(UIImage*)image
    {
    
        OSImageViewController *_imageViewerController = [[OSImageViewController alloc] init];
        UIImage *img = [[UIImage alloc] initWithData:UIImagePNGRepresentation(image)];
    
        _imageViewerController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
        _imageViewerController.modalPresentationStyle = UIModalPresentationFullScreen;
        _imageViewerController.delegate = self;
        [self presentViewController:_imageViewerController animated:YES completion:^(void){
            [_imageViewerController setImage:img];
    
        }];
    
    }