UIMenuController not showing custom action on UICollectionViewController subclass - ios

I am trying to display a custom action using UIMenuController on a UICollectionViewController subclass, and even though the cut, copy and paste actions appears as expected, for some reason my custom action doesn't.
I followed a lot of references from the web but none of them makes it work, here the code:
class CollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
fileprivate var items = [MyClass]()
// MARK: - UICollectionViewDataSource
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellIdentifier", for: indexPath)
/* update cell properties */
return cell
}
// MARK: - UICollectionViewDelegate
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: itemSize, height: itemSize)
}
override func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
return true
}
override func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) {
/* Do Something */
}
override func collectionView(_ collectionView: UICollectionView, shouldShowMenuForItemAt indexPath: IndexPath) -> Bool {
return true
}
public func menuAction(_ sender: UIMenuItem) {
/* Action method*/
}
}
Tried to add the menu item as follows:
let menuItem = UIMenuItem(title: SFLocalization.localizedString("Common-remove"), action: #selector(CollectionViewController.menuAction(_:)))
let menuController = UIMenuController.shared
// menuController.menuItems?.append(menuItem)
menuController.menuItems = [menuItem]
on both viewDidLoad and collectionView(_ collectionView:, shouldShowMenuForItemAt) -> Bool
Any ideas?

Omer - check out this link: http://dev.glide.me/2013/05/custom-item-in-uimenucontroller-of.html
Basically, moving these methods:
(BOOL)canPerformAction:(SEL)action withSender:(id)sender {
(BOOL)canBecomeFirstResponder {
... to the CollectionView cell subclass works. You then have to pass this selector back up to the cell delegate.
With this, I was able to get my custom menu to appear!

Related

UICollectionView shouldShowMenuForItemAt Not Called

I have a stock standard UICollectionView in a UIViewController that is its delegate. However the shouldShowMenuForItemAt func is not called for a long press. I have added a didSelectItemAt func which does get called on clicking a cell to make sure the delegate is indeed wired up correctly.
I also implemented the canPerformAction to return true and performAction in the delegate along with the canPerformAction and canBecomeFirstResponder to return true in my UICollectionViewCell subclass. None of these func's get called for a long press of a cell. Any suggestions?
The missing piece of the puzzle, which most people seem to miss, is that in order for menus to work (in a collection view or table view), the cell must implement the selector.
Here's a minimal example. Instruction: Make a new project using the Single View App template. Copy this code and paste it into ViewController.swift, so as to replace completely everything in that file. Run. Long press on a green square. Enjoy. (The menu item does nothing; the point is, you will see the menu item appear.)
import UIKit
class Cell : UICollectionViewCell {
#objc func f(_ : Any) {}
}
class ViewController: UIViewController {
let cellid = "cellid"
#nonobjc private let howdy = #selector(Cell.f)
override func viewDidLoad() {
super.viewDidLoad()
let cv = UICollectionView(frame: self.view.bounds, collectionViewLayout: UICollectionViewFlowLayout())
self.view.addSubview(cv)
cv.autoresizingMask = [.flexibleWidth, .flexibleHeight]
cv.delegate = self
cv.dataSource = self
cv.register(Cell.self, forCellWithReuseIdentifier: cellid)
}
}
extension ViewController : UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
func collectionView(_ cv: UICollectionView, cellForItemAt ip: IndexPath) -> UICollectionViewCell {
let cell = cv.dequeueReusableCell(withReuseIdentifier: cellid, for: ip)
cell.backgroundColor = .green
return cell
}
func collectionView(_ collectionView: UICollectionView, shouldShowMenuForItemAt indexPath: IndexPath) -> Bool {
let mi = UIMenuItem(title:"Howdy", action:howdy)
UIMenuController.shared.menuItems = [mi]
return true
}
func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
return (action == howdy)
}
func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) {
}
}

Swift: Click onto cell of UICollectionView and open AlertViewController

In my app, I am using an UICollectionView. Now I want to develop an UIAlertController, when clicking onto any cell of the collection view.
I started with following code:
extension HomeViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
…
}
// specify cells
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
….
}
// called when widget is moved
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
…
}
// called when clicked
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
print("Got clicked!")
}
}
But somehow, "Got clicked!" is never printed.
try next:
extension HomeViewController: UICollectionViewDataSource, UICollectionViewDelegate {
}
or
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
...
cell.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap(_:))))
}
func tap(_ sender: UITapGestureRecognizer) {
let location = sender.location(in: self.collectionView)
let indexPath = self.collectionView.indexPathForItem(at: location)
if let index = indexPath {
print("Got clicked on index: \(index)!")
}
}
This is the version using the delegate:
extension HomeViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("item at \(indexPath.section)/\(indexPath.item) tapped")
}
}
Instead of using the extension you can also just add UICollectionViewDelegate and the collectionView(...didSelectItemAt:...) function to the class HomeViewController directly:
class HomeViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("item at \(indexPath.section)/\(indexPath.item) tapped")
}
}
This is because you might have placed some UIButton in the cell. Tap on some empty area in the cell then you will get the 'Got click on index'

Swift open new UIViewController

I'm trying to open new UIViewController when I click on an item inside UICollectionView, but using this code my app crashes and Xcode and simulator restarts so I can't even see where is the problem.
import UIKit
class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.register(CustomCell.self, forCellWithReuseIdentifier: "cellId")
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! CustomCell
cell.imageViewGame.image = UIImage(named: imageArray[indexPath.row])
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width / 2, height: view.frame.width / 2)
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
performSegue(withIdentifier: "postController", sender: title[indexPath.row])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "postController" {
let postController = segue.destination as! PostController
postController.title = sender as! String
}
}
}
Does anyone know from this code why my app crashes? If it's important, I'm not using storyboard.
Try this:
Make you don't forgot to declare postController identifier in your segue
You just can present it modally with:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let postController = PostController()
postController.title = title[indexPath.row]
present(postController, animated: true, completion: nil)
}

UIcollectionview cell issue Swift

I have a simple collectionview controller like this:
class ViewController1: UICollectionViewController{
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView?.delegate = self
self.collectionView?.dataSource = self
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "P", for: indexPath)
return cell
}
}
In the cell there is only an uiimageview and it has this constraints:
My problem is this:
when I execute this code on iPhone 6 simulator I get this (right way):
but when I execute this code on iPhone 5 simulator I get this (wrong way: the imageview starts before the screen on the left and the width and height are wrong respect to the constraints):
I am going crazy for this problem but I dont be able to solve.
Can you help me?
You need to extend your ViewController1 to conform to UICollectionViewDelegateFlowLayout protocol. And implement methods to set size for each collection view cell like so:
class ViewController1: UICollectionViewController, UICollectionViewDelegateFlowLayout {
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView?.delegate = self
self.collectionView?.dataSource = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print(self.collectionView!.frame)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "P", for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.collectionView!.frame.size.width, height: 120.0)
}
}
It has hard coded values, but you should get the idea. Collection View width should be the max size for each item, so they won't get outside the box.

Perform action on selection of UICollectionViewCell?

I would like to call a function when a cell is pressed and I would like to be able to change what that function does based on what cell is pressed. Here is my code currently:
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
//#warning Incomplete method implementation -- Return the number of sections
return 4
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//#warning Incomplete method implementation -- Return the number of items in the section
return 10
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! UICollectionViewCell
// Configure the cell
return cell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
return CGSizeMake(90, 90)
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets
{
return UIEdgeInsetsMake(10, 10, 10, 10)
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat
{
return 10
}
// MARK: UICollectionViewDelegate
// Uncomment this method to specify if the specified item should be highlighted during tracking
override func collectionView(collectionView: UICollectionView, shouldHighlightItemAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
// Uncomment this method to specify if the specified item should be selected
override func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
// Uncomment these methods to specify if an action menu should be displayed for the specified item, and react to actions performed on the item
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 true
}
override func collectionView(collectionView: UICollectionView, performAction action: Selector, forItemAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject?) {
self.printThatItWorked()
}
func printThatItWorked()
{
println("It worked")
}
the function printThatItWorked() should be called whenever a cell is selected, and eventually I would like to be able to have it print that cell number x was pressed. How do I go about doing this?
The delegate method didSelectCell... is where you want to put that code.
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
// You can use indexPath to get "cell number x", or get the cell like:
let cell = collectionView.cellForItemAtIndexPath(indexPath)
printThatItWorked()
}
I figured it out. Implement this:
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
// You can use indexPath to get "cell number x", or get the cell like:
let cell = collectionView.cellForItemAtIndexPath(indexPath)
printThatItWorked()
}

Resources