Swift UICollectionView : didSelectItemAtIndexPath not responding - ios

i have UICollection view in a ViewController and it's not responding to didSelectItemAtIndexPath at all.
// super class
class ViewController: UIViewController, iCarouselDelegate, iCarouselDataSource, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
// delegate
override func viewDidLoad() {
super.viewDidLoad()
// collection view delegate and datasource
collectionView.delegate = self
collectionView.dataSource = self
// did select item
func collectionView(collectionView: UICollectionView!, didSelectItemAtIndexPath indexPath: NSIndexPath!) {
print(indexPath)
}
and this delegate from IB .

several guesses might be helpful:
did you accidentally overriding - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath?
make sure set collectionView.userInteractionEnabled to true
if some high priority UIResponder like UIGestureRecognizer and UIButton added to cell or its subview, corresponding method should be call instead

i had to use another way to run away from this issue and ship my project in time.
i made a button inside my cell and in this method "cellForItemAtIndexPath" and added target for it like so:
imageButton.addTarget(self, action: #selector(ViewController.imageButtonAction(_:)), forControlEvents: UIControlEvents.TouchUpInside)
and passed the selected indexPath.row in this button tag like so :
imageButton.tag = indexPath.row
and this my Action :
func imageButtonAction(sender: UIButton) {
let categoryId = categories[sender.tag]["id"]
let categoryName = categories[sender.tag]["category_name"]
let mainCategory = self.storyboard?.instantiateViewControllerWithIdentifier("MainCategoryViewController") as! MainCategoryViewController
mainCategory.mainCategoryId = categoryId!
mainCategory.title = categoryName!
self.navigationController!.pushViewController(mainCategory, animated: true)
}

Related

How to update a ULabel which is in a viewController from a custom UICollectionViewCell in swift [duplicate]

I have a custom UICollectionViewCell with a button inside it. When I tap the button, an event is fired inside that subclass. I want to then trigger an event on the UICollectionView itself, which I can handle in my view controller.
Pseudo-code:
class MyCell : UICollectionViewCell {
#IBAction func myButton_touchUpInside(_ sender: UIButton) {
// Do stuff, then propagate an event to the UICollectionView
Event.fire("cellUpdated")
}
}
class MyViewController : UIViewController {
#IBAction func collectionView_cellUpdated(_ sender: UICollectionView) {
// Update stuff in the view controller
// to reflect changes made in the collection view
}
}
Ideally, the event I define would appear alongside the default action outlets in the Interface Builder, allowing me to then drag it into my view controller code to create the above collectionView_cellUpdated function, similar to how #IBInspectable works in exposing custom properties.
Is there any way to implement a pattern like this in native Swift 3? Or if not, any libraries that make it possible?
I don't understand your question completely but from what I got, you can simply use a closure to pass the UIButton tap event back to the UIViewController.
Example:
1. Custom UICollectionViewCell
class MyCell: UICollectionViewCell
{
var tapHandler: (()->())?
#IBAction func myButton_touchUpInside(_ sender: UIButton)
{
tapHandler?()
}
}
2. MyViewController
class MyViewController: UIViewController, UICollectionViewDataSource
{
//YOUR CODE..
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCell
cell.tapHandler = {
//Here you can do your custom handling
}
return cell
}
}
Let me know if you face any issues.
Best thing to do is to make a custom protocol for your custom cell class
protocol CustomCellProtocolDelegate {
func custom(cell: YourCellClass, hadButton: UIButton, pressedWithInfo : [String:Any]?)
}
Make this cell class have this protocol as a peculiar delegate, and to trigger this delegate:
class YourCellClass: UICollectionViewCell {
var delegate : CustomCellProtocolDelegate?
var indexPath : IndexPath? //Good practice here to have an indexPath parameter
var yourButton = UIButton()
init(frame: CGRect) {
super.init(frame: frame)
yourButton.addTarget(self, selector: #selector(triggerButton(sender:)))
}
func triggerButton(sender: UIButton) {
if let d = self.delegate {
d.custom(cell: self, hadButton: sender, pressedWithInfo : /*Add info if you want*/)
}
}
}
In your controller, you conform it to the delegate, and you apply the delegate to each cell in cellForItem: atIndexPath:
class YourControllerThatHasTheCollectionView : UIViewController, CustomCellProtocolDelegate {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "identifier", for: indexPath) as! YourCellClass
cell.delegate = self
cell.indexPath = indexPath
return cell
}
func custom(cell: YourCellClass, hadButton: UIButton, pressedWithInfo : [String:Any]?) {
//Here you can process which button was selected, etc.. and apply your changes to your collectionview
}
}
Best practice is to pass the cell's indexPath parameter in the delegate method inside of pressedWithInfo. It saves you the trouble of calculating which cell actually was pressed; hence why i usually add an indexPath element to each of my UICollectionViewCell subclasses. Better yet, include the index inside the protocol method:
protocol CustomCellProtocolDelegate {
func custom(cell: YourCellClass, hadButton: UIButton, pressedAt: IndexPath, withInfo : [String:Any]?)
}
func triggerButton(sender: UIButton) {
if let d = self.delegate {
d.custom(cell: self, hadButton: sender, pressedAt: indexPath!, withInfo : /*Add info if you want*/)
}
}

how to notify the controller when something in a uicollectionviewcell is pressed

I have an extension of UICollectionViewCell class. When something is pressed in this cell, I am trying to notify the Controller. I am not quite sure if the protocol delegate pattern is the way to go about it. I am not sure how to use it in this case. I have the following class outside my extension of UICollectionViewCell class.
protocol bundleThreadsDelegate: class {
func bundleThreadsDidSelect(_ viewController: UIViewController)
}
And I have the following property:
public weak var delegate: bundleThreadsDelegate? in my extension.
I am not quite sure where to go on from Here. Please help.
You said "when something is pressed in this cell", not when the cell itself is pressed, so I assume you may want multiple actionable items in your cell. If that's what you really mean then in your UICollectionViewCell, you could simply add a UIButton (no need for delegates as mentioned here because everything can happen within the same view controller—use delegates when communicating between different objects):
class MyCollectionViewCell: UICollectionViewCell {
let someButton = UIButton()
...
}
When you create the UICollectionView, to make it easiest, set the view controller it's in as the data source:
let myCollection = UICollectionView(frame: .zero, collectionViewLayout: MyCollectionViewFlowLayout())
myCollection.dataSource = self
...
Then in your data source, which would be in something like MyViewController, give the button a target:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let path = indexPath.item
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCell", for: indexPath) as! MyCollectionViewCell
cell.someButton.addTarget(self, action: #selector(someButtonAction(_:)), for: .touchUpInside)
return cell
}
And make sure that the action method is also in MyViewController along with the data source.
#objc func someButtonAction(_ sender: UIButton) {
print("My collection view cell was tapped")
}
Now you can have multiple buttons within one collection view cell that do different things. You can also pass in arguments from the cell to the button action for further customization.
However, if you want action when the entire cell is pressed, use the delegate method already mentioned (or make the entire cell a UIButton which is not as elegant but that's open to interpretation).
Use this CollectionView Delegate method to notify the ViewController when your cell is selected by a user.
func collectionView(_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath)
Chris is correct, assuming you're only interested in the whole cell being selected. If you have a button or something within your cell and it's that press event you're interested in then yeah, you could use a delegate.
As an aside, protocols usually start with an uppercase letter, i.e. BundleThreadsDelegate rather than bundleThreadsDelegate, but that's up to you. Your general approach could be something like this (note this is pseudo code):
protocol YourProtocol {
func didPressYourButton()
}
class YourCell {
#IBOutlet weak var yourButton: UIButton!
public weak var yourButtonDelegate: YourProtocol?
func awakeFromNib() {
super.awakeFromNib()
yourButton.addTarget(self, action: #selector(didPressYourButton), for: .touchUpInside)
}
func didPressYourButton() {
yourButtonDelegate?.didPressYourButton()
}
}
And then in your view controller's cellForRowAt function:
let cell = ...
cell.yourButtonDelegate = self
Then conform to the protocol and implement the method in your view controller:
extension YourViewController: YourProtocol {
func didPressYourButton() {
doAllTheThings()
}
}

didSelect does not work in UICollectionviewCell to select UICollectionViewController

I wrote didSelectItemAtIndexPath func in UICollectionViewCell to select a UICollectionViewController. I wrote the code in two ways but it doesnot work at all. Also, I don't getting an error in there.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if indexPath.item == 0 {
let layout = UICollectionViewFlowLayout()
let controller1 = DigitalSLRCon(collectionViewLayout: layout)
let nav = UINavigationController()
nav.pushViewController(controller1, animated: true)
// OR
let layout = UICollectionViewFlowLayout()
let controller1 = DigitalSLRCon(collectionViewLayout: layout)
navigationController?.pushViewController(controller1, animated: true)
}
didSelectItemAtIndexpathis a function of UICollectionViewDelegate - you don't implement it in the cell, but in your CollectionView's delegate, so probably the view controller which contains the collection view.
Make the UIViewController that holds the collection view conform to UICollectionViewDelegate protocol
Assign that view controller to collection view's delegate property
Implement the didSelectItemAtIndexpath function in the view controller
Please remove the delegates from the UICollectionViewCell The delegates are not for the cell but the UICollectionView handler
If you want to change some element in the cell
you can override the default variables in the cell like this
override var isSelected: Bool {
didSet {
if isSelected {
//do something
} else {
//not selected
}
}
}
override var isHighlighted: Bool {
didSet {
if isHighlighted {
//do something
} else {
//not selected
}
}
}

Can´t add items to UICollectionView inside UIView xib

Objective
I wanna place my (BusinessViewTableHeader: UIView) as tableView header:
tableView.tableHeaderView = BusinessViewTableHeader.instanceFromNib() as! BusinessViewTableHeader
Inside BusinessViewTableHeader there is a UICollectionView which are supposed to display images when swiped, much like the Tinder app.
This is my UIView subclass:
class BusinessViewTableHeader: UIView {
#IBOutlet var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
self.collectionView.delegate = self
self.collectionView.registerNib(UINib(nibName: "BusinessImageCollectionCell", bundle: nil), forCellWithReuseIdentifier: "BusinessImageCollectionCell")
}
class func instanceFromNib() -> UIView {
return UINib(nibName: "BusinessViewTableHeader", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
}
....
}
extension BusinessViewTableHeader: UICollectionViewDelegate, UICollectionViewDataSource {
....
}
Problem
I have a custom UIView xib containing a UICollectionView. The problem is that I can´t add any cells (items) to the UICollectionView. I can add items to my other UICollectionView which are placed inside a UIViewController. The first image is showing the properties for the UICollectionView inside a UIViewController, the second image is showing the UICollectionView inside a UIView xib.
[![UICollectionView in UIViewController][1]][1]
[1]: http://i.stack.imgur.com/zFCeG.png
[![UICollectionView in UIView xib][2][2]
[2]: http://i.stack.imgur.com/jKU6z.png
Question
Why am I not able to add items to the UICollectionView inside the UIView xib? How?
You can't have UICollectionViewCell when the UICollectionView is on a Nib. What you need to do is to create the UICollectionViewCell as another nib and get it registered in the class that you are using for your CollectionView.
Create a new nib, drag a UICollectionViewCell inside it, and do something like this in the class that works with your UICollectionView.
override func awakeFromNib() {
let nibName = UINib(nibName: "ClassCollectionCell", bundle:nil)
collectionView.registerNib(nibName, forCellWithReuseIdentifier: "collectionCell")
}
Remember you can add a custom class to the UICollectionViewCell so you can pass dynamic data to it.
Adding cells in a xib is not supported. If you must use a xib file, then you will need a separate xib which contains the UICollectionView cell. Storyboards may be a better solution.
It is not clear what you are trying to achieve. UICollectionView has specific means for creating headers which uses the datasource and delegate. Collection views are good for displaying items in a grid layout or other complex arrangements.
If all you need is to display a list of rows, then a UITableViewController might be an easier alternative.
Whatever the case, it is probably better to use a storyboard instead of a xib, and to subclass the UICollectionViewController or UITableViewController, rather than a subview.
Your custom class name can be entered in the identity inspector for the UIViewController or UIView:
In Swift 3.0 register nib with following method-
let nibName = UINib(nibName: "FruitCell", bundle:nil)
collectionView.register(nibName, forCellWithReuseIdentifier: "CellIdentifier")
In Swift 3.0
first Create Xibfile of UIView
import UIKit
class SubCatagoryListView:UIView , UICollectionViewDelegate , UICollectionViewDataSource
{
#IBOutlet weak var mainView: UIView!
#IBOutlet weak var btnClose: UIButton!
#IBOutlet weak var subCategoryListCollectionView: UICollectionView!
#IBOutlet weak var lblTitle: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
subCategoryListCollectionView.register(UINib(nibName: "SubcatagoryListCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "SubcatagoryListCollectionViewCell")
mainView.layer.cornerRadius = 10.0
mainView.clipsToBounds = true
}
static func subCatagoryListView() -> SubCatagoryListView? {
let arr = Bundle.main.loadNibNamed("SubCatagoryListView", owner: self, options: nil)
if arr != nil {
if arr!.count > 0 {
if let view = arr![0] as? SubCatagoryListView {
return view;
}
}
}
return nil;
}
#IBAction func btnBackgroundTapped(_ sender: UIButton)
{
self.removeFromSuperview()
}
#IBAction func btnCloseTapped(_ sender: UIButton)
{
self.removeFromSuperview()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SubcatagoryListCollectionViewCell", for: indexPath) as! SubcatagoryListCollectionViewCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
let cellsize = CGSize(width: (subCategoryListCollectionView.bounds.size.width/3) - 10, height: 50)
return cellsize
}
}
After Create new CollectionViewCell Xib file
import UIKit
class SubcatagoryListCollectionViewCell: UICollectionViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
after create both file i am load xib on my storybord
var subcatagoryXib:SubCatagoryListView?
override func awakeFromNib() {
super.awakeFromNib()
if let subcategoryView = SubCatagoryListView.subCatagoryListView()
{
subcatagoryXib = subcategoryView
}
}
#IBAction func btnAddInterestTapped(_ sender: UIButton) {
if subcatagoryXib != nil
{
self.subcatagoryXib!.frame = CGRect(x:0, y: 0, width: self.view.frame.width , height: self.view.frame.height)
self.view.addSubview(self.subcatagoryXib!)
}
}
Same as #Pato's answer, but here is a more thorough tutorial for how to add a customized UICollectionViewCell inside a Xib file from #aestusLabs on Medium. It's a 3-5 min reading, I personally find it very helpful. It basically tells you to create another customized UICollectionViewCell with .xib, and register it in your "level 1" cell's awakeFromNib().
https://medium.com/#aestusLabs/adding-a-uicollectionviews-to-a-custom-uitableviewcell-xib-tutorial-swift-4-xcode-9-2-1ec9ce4095d3

How to call an outside function from button in cell

I use a main class call newsFeedCointroller as UICollectionViewController.
1. In cell inside I have a newsfeed with a like button (to populate the cell I use a class called "FeedCell")
2. Out from cells (in mainview) I have a label (labelX) used for "splash message" with a function called "messageAnimated"
How can I call the "messageAnimated" function from the button inside the cells.
I want to to change the label text to for example: "you just liked it"...
Thanks for helping me
In your FeedCell you should declare a delegate (Read about delegate pattern here)
protocol FeedCellDelegate {
func didClickButtonLikeInFeedCell(cell: FeedCell)
}
In your cell implementation (suppose that you add target manually)
var delegate: FeedCellDelegate?
override func awakeFromNib() {
self.likeButton.addTarget(self, action: #selector(FeedCell.onClickButtonLike(_:)), forControlEvents: .TouchUpInside)
}
func onClickButtonLike(sender: UIButton) {
self.delegate?.didClickButtonLikeInFeedCell(self)
}
In your View controller
extension FeedViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("feedCell", forIndexPath: indexPath) as! FeedCell
// Do your setup.
// ...
// Then here, set the delegate
cell.delegate = self
return cell
}
// I don't care about other delegate functions, it's up to you.
}
extension FeedViewController: FeedCellDelegate {
func didClickButtonLikeInFeedCell(cell: FeedCell) {
// Do whatever you want to do when click the like button.
let indexPath = collectionView.indexPathForCell(cell)
print("Button like clicked from cell with indexPath \(indexPath)")
messageAnimated()
}
}

Resources