From 0e9c9a9bb34b10edde397124017005bd3cfe40e2 Mon Sep 17 00:00:00 2001 From: sdkjfhsdkjhfsdlkjhfsdf Date: Tue, 19 Dec 2017 12:35:39 -0600 Subject: [PATCH] Separate gestures for text/vs media // FREEBIE --- .../ConversationView/Cells/OWSMessageCell.m | 214 ++++++++++++------ .../ConversationView/ConversationViewItem.h | 12 +- .../ConversationView/ConversationViewItem.m | 140 ++++++++++-- .../attachments/FullImageViewController.m | 49 +++- 4 files changed, 315 insertions(+), 100 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m index 8d11c6f67..4e37aaa64 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m @@ -256,13 +256,21 @@ NS_ASSUME_NONNULL_BEGIN [self.footerView autoPinEdgeToSuperviewEdge:ALEdgeBottom]; [self.footerView autoPinWidthToSuperview]; - UITapGestureRecognizer *tap = - [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]; - [self addGestureRecognizer:tap]; + UITapGestureRecognizer *mediaTap = + [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleMediaTapGesture:)]; + [self.mediaMaskingView addGestureRecognizer:mediaTap]; - UILongPressGestureRecognizer *longPress = - [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)]; - [self addGestureRecognizer:longPress]; + UITapGestureRecognizer *textTap = + [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTextTapGesture:)]; + [self.textBubbleImageView addGestureRecognizer:textTap]; + + UILongPressGestureRecognizer *mediaLongPress = + [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleMediaLongPressGesture:)]; + [self.mediaMaskingView addGestureRecognizer:mediaLongPress]; + + UILongPressGestureRecognizer *textLongPress = + [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleTextLongPressGesture:)]; + [self.textBubbleImageView addGestureRecognizer:textLongPress]; PanDirectionGestureRecognizer *panGesture = [[PanDirectionGestureRecognizer alloc] initWithDirection:PanDirectionHorizontal @@ -337,6 +345,14 @@ NS_ASSUME_NONNULL_BEGIN return self.viewItem.messageCellType; } +- (BOOL)hasText +{ + // This should always be valid for the appropriate cell types. + OWSAssert(self.viewItem); + + return self.viewItem.hasText; +} + - (nullable DisplayableText *)displayableText { // This should always be valid for the appropriate cell types. @@ -830,7 +846,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)addCaptionIfNecessary { - if (self.viewItem.hasText) { + if (self.hasText) { [self loadForTextDisplay]; } else { [self.contentConstraints addObject:[self.textBubbleImageView autoSetDimension:ALDimensionHeight toSize:0]]; @@ -949,7 +965,7 @@ NS_ASSUME_NONNULL_BEGIN self.mediaMaskingView.isOutgoing = self.isOutgoing; // Hide tail on attachments followed by a caption - self.mediaMaskingView.hideTail = self.viewItem.hasText; + self.mediaMaskingView.hideTail = self.hasText; self.mediaMaskingView.maskedSubview = view; [self.mediaMaskingView updateMask]; } @@ -1002,7 +1018,7 @@ NS_ASSUME_NONNULL_BEGIN CGSize mediaContentSize = CGSizeZero; CGSize textContentSize = CGSizeZero; - if (self.viewItem.hasText) { + if (self.hasText) { textContentSize = [self textBubbleSizeForContentWidth:contentWidth]; } switch (self.cellType) { @@ -1208,72 +1224,110 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Gesture recognizers -- (void)handleTapGesture:(UITapGestureRecognizer *)sender +- (void)handleTextTapGesture:(UITapGestureRecognizer *)sender { OWSAssert(self.delegate); - if (sender.state == UIGestureRecognizerStateRecognized) { + if (sender.state != UIGestureRecognizerStateRecognized) { + DDLogInfo(@"%@ Ignoring tap on message: %@", self.logTag, self.viewItem.interaction.debugDescription); + return; + } - if (self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) { - TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction; - if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent) { - [self.delegate didTapFailedOutgoingMessage:outgoingMessage]; - return; - } else if (outgoingMessage.messageState == TSOutgoingMessageStateAttemptingOut) { - // Ignore taps on outgoing messages being sent. - return; - } + if (self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) { + TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction; + if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent) { + [self.delegate didTapFailedOutgoingMessage:outgoingMessage]; + return; + } else if (outgoingMessage.messageState == TSOutgoingMessageStateAttemptingOut) { + // Ignore taps on outgoing messages being sent. + return; } + } - switch (self.cellType) { - case OWSMessageCellType_TextMessage: - case OWSMessageCellType_OversizeTextMessage: - if (self.displayableText.isTextTruncated) { - [self.delegate didTapTruncatedTextMessage:self.viewItem]; - return; - } - break; - case OWSMessageCellType_StillImage: - [self.delegate didTapImageViewItem:self.viewItem - attachmentStream:self.attachmentStream - imageView:self.stillImageView]; - break; - case OWSMessageCellType_AnimatedImage: - [self.delegate didTapImageViewItem:self.viewItem - attachmentStream:self.attachmentStream - imageView:self.animatedImageView]; - break; - case OWSMessageCellType_Audio: - [self.delegate didTapAudioViewItem:self.viewItem attachmentStream:self.attachmentStream]; - return; - case OWSMessageCellType_Video: - [self.delegate didTapVideoViewItem:self.viewItem attachmentStream:self.attachmentStream]; + if (self.hasText && self.displayableText.isTextTruncated) { + [self.delegate didTapTruncatedTextMessage:self.viewItem]; + return; + } +} + +- (void)handleMediaTapGesture:(UITapGestureRecognizer *)sender +{ + OWSAssert(self.delegate); + + if (sender.state != UIGestureRecognizerStateRecognized) { + DDLogInfo(@"%@ Ignoring tap on message: %@", self.logTag, self.viewItem.interaction.debugDescription); + return; + } + + if (self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) { + TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction; + if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent) { + [self.delegate didTapFailedOutgoingMessage:outgoingMessage]; + return; + } else if (outgoingMessage.messageState == TSOutgoingMessageStateAttemptingOut) { + // Ignore taps on outgoing messages being sent. + return; + } + } + + switch (self.cellType) { + case OWSMessageCellType_Unknown: + break; + case OWSMessageCellType_TextMessage: + case OWSMessageCellType_OversizeTextMessage: + if (self.displayableText.isTextTruncated) { + [self.delegate didTapTruncatedTextMessage:self.viewItem]; return; - case OWSMessageCellType_GenericAttachment: - [AttachmentSharing showShareUIForAttachment:self.attachmentStream]; - break; - case OWSMessageCellType_DownloadingAttachment: { - OWSAssert(self.attachmentPointer); - if (self.attachmentPointer.state == TSAttachmentPointerStateFailed) { - [self.delegate didTapFailedIncomingAttachment:self.viewItem - attachmentPointer:self.attachmentPointer]; - } - break; } + break; + case OWSMessageCellType_StillImage: + [self.delegate didTapImageViewItem:self.viewItem + attachmentStream:self.attachmentStream + imageView:self.stillImageView]; + break; + case OWSMessageCellType_AnimatedImage: + [self.delegate didTapImageViewItem:self.viewItem + attachmentStream:self.attachmentStream + imageView:self.animatedImageView]; + break; + case OWSMessageCellType_Audio: + [self.delegate didTapAudioViewItem:self.viewItem attachmentStream:self.attachmentStream]; + return; + case OWSMessageCellType_Video: + [self.delegate didTapVideoViewItem:self.viewItem attachmentStream:self.attachmentStream]; + return; + case OWSMessageCellType_GenericAttachment: + [AttachmentSharing showShareUIForAttachment:self.attachmentStream]; + break; + case OWSMessageCellType_DownloadingAttachment: { + OWSAssert(self.attachmentPointer); + if (self.attachmentPointer.state == TSAttachmentPointerStateFailed) { + [self.delegate didTapFailedIncomingAttachment:self.viewItem attachmentPointer:self.attachmentPointer]; + } + break; } + } +} - DDLogInfo(@"%@ Ignoring tap on message: %@", self.logTag, self.viewItem.interaction.debugDescription); +- (void)handleTextLongPressGesture:(UILongPressGestureRecognizer *)sender +{ + OWSAssert(self.delegate); + + // We "eagerly" respond when the long press begins, not when it ends. + if (sender.state == UIGestureRecognizerStateBegan) { + CGPoint location = [sender locationInView:self]; + [self showTextMenuController:location]; } } -- (void)handleLongPressGesture:(UILongPressGestureRecognizer *)sender +- (void)handleMediaLongPressGesture:(UILongPressGestureRecognizer *)sender { OWSAssert(self.delegate); // We "eagerly" respond when the long press begins, not when it ends. if (sender.state == UIGestureRecognizerStateBegan) { CGPoint location = [sender locationInView:self]; - [self showMenuController:location]; + [self showMediaMenuController:location]; } } @@ -1286,7 +1340,29 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - UIMenuController -- (void)showMenuController:(CGPoint)fromLocation +- (void)showTextMenuController:(CGPoint)fromLocation +{ + // We don't want taps on messages to hide the keyboard, + // so we only let messages become first responder + // while they are trying to present the menu controller. + self.isPresentingMenuController = YES; + + [self becomeFirstResponder]; + + if ([UIMenuController sharedMenuController].isMenuVisible) { + [[UIMenuController sharedMenuController] setMenuVisible:NO animated:NO]; + } + + // We use custom action selectors so that we can control + // the ordering of the actions in the menu. + NSArray *menuItems = self.viewItem.textMenuControllerItems; + [UIMenuController sharedMenuController].menuItems = menuItems; + CGRect targetRect = CGRectMake(fromLocation.x, fromLocation.y, 1, 1); + [[UIMenuController sharedMenuController] setTargetRect:targetRect inView:self]; + [[UIMenuController sharedMenuController] setMenuVisible:YES animated:YES]; +} + +- (void)showMediaMenuController:(CGPoint)fromLocation { // We don't want taps on messages to hide the keyboard, // so we only let messages become first responder @@ -1301,7 +1377,7 @@ NS_ASSUME_NONNULL_BEGIN // We use custom action selectors so that we can control // the ordering of the actions in the menu. - NSArray *menuItems = self.viewItem.menuControllerItems; + NSArray *menuItems = self.viewItem.mediaMenuControllerItems; [UIMenuController sharedMenuController].menuItems = menuItems; CGRect targetRect = CGRectMake(fromLocation.x, fromLocation.y, 1, 1); [[UIMenuController sharedMenuController] setTargetRect:targetRect inView:self]; @@ -1313,19 +1389,29 @@ NS_ASSUME_NONNULL_BEGIN return [self.viewItem canPerformAction:action]; } -- (void)copyAction:(nullable id)sender +- (void)copyTextAction:(nullable id)sender +{ + [self.viewItem copyTextAction]; +} + +- (void)copyMediaAction:(nullable id)sender +{ + [self.viewItem copyMediaAction]; +} + +- (void)shareTextAction:(nullable id)sender { - [self.viewItem copyAction]; + [self.viewItem shareTextAction]; } -- (void)shareAction:(nullable id)sender +- (void)shareMediaAction:(nullable id)sender { - [self.viewItem shareAction]; + [self.viewItem shareMediaAction]; } -- (void)saveAction:(nullable id)sender +- (void)saveMediaAction:(nullable id)sender { - [self.viewItem saveAction]; + [self.viewItem saveMediaAction]; } - (void)deleteAction:(nullable id)sender diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h index 60b7d7e35..86eb60411 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h @@ -90,11 +90,15 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); #pragma mark - UIMenuController -- (NSArray *)menuControllerItems; +- (NSArray *)textMenuControllerItems; +- (NSArray *)mediaMenuControllerItems; + - (BOOL)canPerformAction:(SEL)action; -- (void)copyAction; -- (void)shareAction; -- (void)saveAction; +- (void)copyMediaAction; +- (void)copyTextAction; +- (void)shareMediaAction; +- (void)shareTextAction; +- (void)saveMediaAction; - (void)deleteAction; - (SEL)metadataActionSelector; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m index b4a0963f2..e61564bdd 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m @@ -34,6 +34,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) return @"OWSMessageCellType_GenericAttachment"; case OWSMessageCellType_DownloadingAttachment: return @"OWSMessageCellType_DownloadingAttachment"; + case OWSMessageCellType_Unknown: + return @"OWSMessageCellType_Unknown"; } } @@ -478,41 +480,70 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) #pragma mark - UIMenuController -- (NSArray *)menuControllerItems +- (NSArray *)textMenuControllerItems +{ + return @[ + [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"EDIT_ITEM_COPY_ACTION", + @"Short name for edit menu item to copy contents of media message.") + action:self.copyTextActionSelector], + [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"EDIT_ITEM_SHARE_ACTION", + @"Short name for edit menu item to share contents of media message.") + action:self.shareTextActionSelector], + [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"EDIT_ITEM_MESSAGE_METADATA_ACTION", + @"Short name for edit menu item to show message metadata.") + action:self.metadataActionSelector], + // FIXME this is going to be confusing if the text/attachment look like separate entities. + // we can re-enable this once it's clear that deleting the text would also delete the attachment. + // [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"EDIT_ITEM_DELETE_ACTION", + // @"Short name for edit menu item to delete contents of media + // message.") + // action:self.deleteActionSelector], + ]; +} +- (NSArray *)mediaMenuControllerItems { return @[ [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"EDIT_ITEM_SHARE_ACTION", @"Short name for edit menu item to share contents of media message.") - action:self.shareActionSelector], + action:self.shareMediaActionSelector], [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"EDIT_ITEM_MESSAGE_METADATA_ACTION", @"Short name for edit menu item to show message metadata.") action:self.metadataActionSelector], [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"EDIT_ITEM_COPY_ACTION", @"Short name for edit menu item to copy contents of media message.") - action:self.copyActionSelector], + action:self.copyMediaActionSelector], [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"EDIT_ITEM_DELETE_ACTION", @"Short name for edit menu item to delete contents of media message.") action:self.deleteActionSelector], - // TODO: Do we want a save action? [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"EDIT_ITEM_SAVE_ACTION", @"Short name for edit menu item to save contents of media message.") - action:self.saveActionSelector], + action:self.saveMediaActionSelector], ]; } -- (SEL)copyActionSelector +- (SEL)copyTextActionSelector +{ + return NSSelectorFromString(@"copyTextAction:"); +} + +- (SEL)copyMediaActionSelector { - return NSSelectorFromString(@"copyAction:"); + return NSSelectorFromString(@"copyMediaAction:"); } -- (SEL)saveActionSelector +- (SEL)saveMediaActionSelector { - return NSSelectorFromString(@"saveAction:"); + return NSSelectorFromString(@"saveMediaAction:"); } -- (SEL)shareActionSelector +- (SEL)shareTextActionSelector { - return NSSelectorFromString(@"shareAction:"); + return NSSelectorFromString(@"shareTextAction:"); +} + +- (SEL)shareMediaActionSelector +{ + return NSSelectorFromString(@"shareMediaAction:"); } - (SEL)deleteActionSelector @@ -528,12 +559,16 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) // We only use custom actions in UIMenuController. - (BOOL)canPerformAction:(SEL)action { - if (action == self.copyActionSelector) { - return [self hasActionContent]; - } else if (action == self.saveActionSelector) { - return [self canSave]; - } else if (action == self.shareActionSelector) { - return [self hasActionContent]; + if (action == self.copyTextActionSelector) { + return [self hasTextActionContent]; + } else if (action == self.copyMediaActionSelector) { + return [self hasMediaActionContent]; + } else if (action == self.saveMediaActionSelector) { + return [self canSaveMedia]; + } else if (action == self.shareTextActionSelector) { + return [self hasTextActionContent]; + } else if (action == self.shareMediaActionSelector) { + return [self hasMediaActionContent]; } else if (action == self.deleteActionSelector) { return YES; } else if (action == self.metadataActionSelector) { @@ -543,14 +578,40 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) } } -- (void)copyAction +- (void)copyTextAction { switch (self.messageCellType) { case OWSMessageCellType_TextMessage: case OWSMessageCellType_OversizeTextMessage: + case OWSMessageCellType_StillImage: + case OWSMessageCellType_AnimatedImage: + case OWSMessageCellType_Audio: + case OWSMessageCellType_Video: + case OWSMessageCellType_GenericAttachment: { OWSAssert(self.displayableText); [UIPasteboard.generalPasteboard setString:self.displayableText.fullText]; break; + } + case OWSMessageCellType_DownloadingAttachment: { + OWSFail(@"%@ Can't copy not-yet-downloaded attachment", self.logTag); + break; + } + case OWSMessageCellType_Unknown: { + OWSFail(@"%@ No text to copy", self.logTag); + break; + } + } +} + +- (void)copyMediaAction +{ + switch (self.messageCellType) { + case OWSMessageCellType_Unknown: + case OWSMessageCellType_TextMessage: + case OWSMessageCellType_OversizeTextMessage: { + OWSFail(@"%@ No media to copy", self.logTag); + break; + } case OWSMessageCellType_StillImage: case OWSMessageCellType_AnimatedImage: case OWSMessageCellType_Audio: @@ -576,14 +637,38 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) } } -- (void)shareAction +- (void)shareTextAction { switch (self.messageCellType) { case OWSMessageCellType_TextMessage: case OWSMessageCellType_OversizeTextMessage: + case OWSMessageCellType_StillImage: + case OWSMessageCellType_AnimatedImage: + case OWSMessageCellType_Audio: + case OWSMessageCellType_Video: + case OWSMessageCellType_GenericAttachment: { OWSAssert(self.displayableText); [AttachmentSharing showShareUIForText:self.displayableText.fullText]; break; + } + case OWSMessageCellType_DownloadingAttachment: { + OWSFail(@"%@ Can't share not-yet-downloaded attachment", self.logTag); + break; + } + case OWSMessageCellType_Unknown: { + OWSFail(@"%@ No text to share", self.logTag) + } + } +} + +- (void)shareMediaAction +{ + switch (self.messageCellType) { + case OWSMessageCellType_Unknown: + case OWSMessageCellType_TextMessage: + case OWSMessageCellType_OversizeTextMessage: + OWSFail(@"No media to share."); + break; case OWSMessageCellType_StillImage: case OWSMessageCellType_AnimatedImage: case OWSMessageCellType_Audio: @@ -598,9 +683,10 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) } } -- (BOOL)canSave +- (BOOL)canSaveMedia { switch (self.messageCellType) { + case OWSMessageCellType_Unknown: case OWSMessageCellType_TextMessage: case OWSMessageCellType_OversizeTextMessage: return NO; @@ -619,9 +705,10 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) } } -- (void)saveAction +- (void)saveMediaAction { switch (self.messageCellType) { + case OWSMessageCellType_Unknown: case OWSMessageCellType_TextMessage: case OWSMessageCellType_OversizeTextMessage: OWSFail(@"%@ Cannot save text data.", self.logTag); @@ -668,13 +755,18 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) [self.interaction remove]; } -- (BOOL)hasActionContent +- (BOOL)hasTextActionContent +{ + return self.hasText && self.displayableText.fullText.length > 0; +} + +- (BOOL)hasMediaActionContent { switch (self.messageCellType) { + case OWSMessageCellType_Unknown: case OWSMessageCellType_TextMessage: case OWSMessageCellType_OversizeTextMessage: - OWSAssert(self.displayableText); - return self.displayableText.fullText.length > 0; + return NO; case OWSMessageCellType_StillImage: case OWSMessageCellType_AnimatedImage: case OWSMessageCellType_Audio: diff --git a/SignalMessaging/attachments/FullImageViewController.m b/SignalMessaging/attachments/FullImageViewController.m index 1dbad385c..b6e251db9 100644 --- a/SignalMessaging/attachments/FullImageViewController.m +++ b/SignalMessaging/attachments/FullImageViewController.m @@ -311,7 +311,7 @@ NS_ASSUME_NONNULL_BEGIN } } -- (void)longPressGesture:(UIGestureRecognizer *)sender +- (void)longPressTextGesture:(UIGestureRecognizer *)sender { // We "eagerly" respond when the long press begins, not when it ends. if (sender.state == UIGestureRecognizerStateBegan) { @@ -325,7 +325,30 @@ NS_ASSUME_NONNULL_BEGIN [[UIMenuController sharedMenuController] setMenuVisible:NO animated:NO]; } - NSArray *menuItems = self.viewItem.menuControllerItems; + NSArray *menuItems = self.viewItem.textMenuControllerItems; + [UIMenuController sharedMenuController].menuItems = menuItems; + CGPoint location = [sender locationInView:self.view]; + CGRect targetRect = CGRectMake(location.x, location.y, 1, 1); + [[UIMenuController sharedMenuController] setTargetRect:targetRect inView:self.view]; + [[UIMenuController sharedMenuController] setMenuVisible:YES animated:YES]; + } +} + +- (void)longPressMediaGesture:(UIGestureRecognizer *)sender +{ + // We "eagerly" respond when the long press begins, not when it ends. + if (sender.state == UIGestureRecognizerStateBegan) { + if (!self.viewItem) { + return; + } + + [self.view becomeFirstResponder]; + + if ([UIMenuController sharedMenuController].isMenuVisible) { + [[UIMenuController sharedMenuController] setMenuVisible:NO animated:NO]; + } + + NSArray *menuItems = self.viewItem.mediaMenuControllerItems; [UIMenuController sharedMenuController].menuItems = menuItems; CGPoint location = [sender locationInView:self.view]; CGRect targetRect = CGRectMake(location.x, location.y, 1, 1); @@ -342,19 +365,29 @@ NS_ASSUME_NONNULL_BEGIN return [self.viewItem canPerformAction:action]; } -- (void)copyAction:(nullable id)sender +- (void)copyTextAction:(nullable id)sender +{ + [self.viewItem copyTextAction]; +} + +- (void)copyMediaAction:(nullable id)sender +{ + [self.viewItem copyMediaAction]; +} + +- (void)shareTextAction:(nullable id)sender { - [self.viewItem copyAction]; + [self.viewItem shareTextAction]; } -- (void)shareAction:(nullable id)sender +- (void)shareMediaAction:(nullable id)sender { - [self.viewItem shareAction]; + [self.viewItem shareMediaAction]; } -- (void)saveAction:(nullable id)sender +- (void)saveMediaAction:(nullable id)sender { - [self.viewItem saveAction]; + [self.viewItem saveMediaAction]; } - (void)deleteAction:(nullable id)sender