I have added custom menu controller when long press on UICollectionViewCell
[self becomeFirstResponder];
UIMenuItem *menuItem = [[UIMenuItem alloc] i
Maybe a bit late but i maybe found a better solution for those who are still search for this:
In viewDidLoad of your UICollectionViewController add your item:
UIMenuItem *menuItem = [[UIMenuItem alloc] initWithTitle:@"Title" action:@selector(action:)];
[[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObject:menuItem]];
Add the following delegate methods:
//This method is called instead of canPerformAction for each action (copy, cut and paste too)
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
if (action == @selector(action:)) {
return YES;
}
return NO;
}
//Yes for showing menu in general
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
Subclass UICollectionViewCell if you didn't already. Add the method you specified for your item:
- (void)action:(UIMenuController*)menuController {
}
This way you don't need any becomeFirstResponder or other methods. You have all actions in one place and you can easily handle different cells if you call a general method with the cell itself as a parameter.
Edit: Somehow the uicollectionview needs the existence of this method (this method isn't called for your custom action, i think the uicollectionview just checks for existance)
- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
}
When people have trouble getting menus to work on long press in a collection view (or table view, for that matter), it is always for one of two reasons:
You're using the long press gesture recognizer for something. You cannot, for example, have both dragging and menus in the same collection view.
You've forgotten to implement the selector in the cell.
For example, the OP's code says:
UIMenuItem *menuItem = [[UIMenuItem alloc] initWithTitle:@"Custom Action"
action:@selector(customAction:)];
The implication is that customAction
is a method this class. This is wrong. customAction:
must be a method of the cell class. The reason is that the runtime will look at the cell class and will not show the menu item unless the cell implements the menu item's action method.
For a complete minimal working example (in Swift), see my answer here: https://stackoverflow.com/a/51898182/341994
I've just spent two days trying to figure out the "correct" way of doing this, and barking up the wrong tree with some of the suggestions that are around.
This article shows the correct way of doing this. I hope that by posting it here someone will be saved a few hours.
http://dev.glide.me/2013/05/custom-item-in-uimenucontroller-of.html
You need to trigger delegate functions from custom UICollectionViewCell
Here is my working sample code for Swift3
CollectionViewController
override func viewDidLoad() {
super.viewDidLoad()
let editMenuItem = UIMenuItem(title: "Edit", action: NSSelectorFromString("editCollection"))
let deleteMenuItem = UIMenuItem(title: "Delete", action: NSSelectorFromString("deleteCollection"))
UIMenuController.shared.menuItems = [editMenuItem, deleteMenuItem]
}
override func collectionView(_ collectionView: UICollectionView, shouldShowMenuForItemAt indexPath: IndexPath) -> Bool {
return true
}
override func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
return action == NSSelectorFromString("editCollection") || action == NSSelectorFromString("deleteCollection")
}
override func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) {
print("action:\(action.description)")
//Custom actions here..
}
Add following functions to your custom UICollectionViewCell
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return action == NSSelectorFromString("editCollection") || action == NSSelectorFromString("deleteCollection")
}
To call delegate function from cell (needs to be in your custom UICollectionViewCell)
func editCollection()
{
let collectionView = self.superview as! UICollectionView
let d:UICollectionViewDelegate = collectionView.delegate!
d.collectionView!(collectionView, performAction: NSSelectorFromString("editCollection"), forItemAt: collectionView.indexPath(for: self)!, withSender: self)
}
func deleteCollection()
{
let collectionView = self.superview as! UICollectionView
let d:UICollectionViewDelegate = collectionView.delegate!
d.collectionView!(collectionView, performAction: NSSelectorFromString("deleteCollection"), forItemAt: collectionView.indexPath(for: self)!, withSender: self)
}
Swift 3 Solution:
Simply do all stuff inside UICollectionView class and assign this class to UICollectionView object.
import UIKit
class MyAppCollectionView: UICollectionView {
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addLongPressGesture()
}
func addLongPressGesture() {
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(MyAppCollectionView.longPressed(_:)))
longPressGesture.minimumPressDuration = 0.5
self.addGestureRecognizer(longPressGesture)
}
func longPressed(_ gesture: UILongPressGestureRecognizer) {
let point = gesture.location(in: self)
let indexPath = self.indexPathForItem(at: point)
if indexPath != nil {
MyAppViewController.cellIndex = indexPath!.row
let editMenu = UIMenuController.shared
becomeFirstResponder()
let custom1Item = UIMenuItem(title: "Custom1", action: #selector(MyAppViewController.custome1Method))
let custom2Item = UIMenuItem(title: "Custom2", action: #selector(MyAppViewController.custome2Method))
editMenu.menuItems = [custom1Item, custom2Item]
editMenu.setTargetRect(CGRect(x: point.x, y: point.y, width: 20, height: 20), in: self)
editMenu.setMenuVisible(true, animated: true)
}
}
override var canBecomeFirstResponder: Bool {
return true
}
}
class MyAppViewController: UIViewController {
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
// You need to only return true for the actions you want, otherwise you get the whole range of
// iOS actions. You can see this by just removing the if statement here.
//For folder edit
if action == #selector(MyAppViewController.custome1Method) {
return true
}
if action == #selector(MyAppViewController.custome2Method) {
return true
}
return false
}
}
On iOS 9 with Swift to SHOW ONLY CUSTOM ITEMS (without the default cut, paste and so on), I only managed to make work with the following code.
On method viewDidLoad
:
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(contextMenuHandler))
longPressRecognizer.minimumPressDuration = 0.3
longPressRecognizer.delaysTouchesBegan = true
self.collectionView?.addGestureRecognizer(longPressRecognizer)
Override method canBecomeFirstResponder
:
override func canBecomeFirstResponder() -> Bool {
return true
}
Override these two collection related methods:
override func collectionView(collectionView: UICollectionView, shouldShowMenuForItemAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
override func collectionView(collectionView: UICollectionView, canPerformAction action: Selector,
forItemAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject?) -> Bool {
return (action == #selector(send) || action == #selector(delete))
}
Create the gesture handler method:
func contextMenuHandler(gesture: UILongPressGestureRecognizer) {
if gesture.state == UIGestureRecognizerState.Began {
let indexPath = self.collectionView?.indexPathForItemAtPoint(gesture.locationInView(self.collectionView))
if indexPath != nil {
self.selectedIndexPath = indexPath!
let cell = self.collectionView?.cellForItemAtIndexPath(self.selectedIndexPath)
let menu = UIMenuController.sharedMenuController()
let sendMenuItem = UIMenuItem(title: "Send", action: #selector(send))
let deleteMenuItem = UIMenuItem(title: "Delete", action: #selector(delete))
menu.setTargetRect(CGRectMake(0, 5, 60, 80), inView: (cell?.contentView)!)
menu.menuItems = [sendMenuItem, deleteMenuItem]
menu.setMenuVisible(true, animated: true)
}
}
}
And, finally, create the selector's methods:
func send() {
print("Send performed!")
}
func delete() {
print("Delete performed!")
}
Hope that helps. :)
Cheers.