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
}
}
}
Related
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*/)
}
}
Currently we have a uicollectionview that is embedded in a tableview cell. When the collection view cell is selected it's suppose to initiate a push segue to another view controller. The problem is there is no option to perform the segue on the cell. Is there a way around it? Here is the cell:
class CastCell : UITableViewCell {
var castPhotosArray: [CastData] = []
let extraImageReuseIdentifier = "castCollectCell"
let detailToPeopleSegueIdentifier = "detailToPeopleSegue"
var castID: NSNumber?
#IBOutlet weak var castCollectiontView: UICollectionView!
override func awakeFromNib() {
castCollectiontView.delegate = self
castCollectiontView.dataSource = self
}
}
extension CastCell: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return castPhotosArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = castCollectiontView.dequeueReusableCell(withReuseIdentifier: extraImageReuseIdentifier, for: indexPath) as! CastCollectionViewCell
cell.actorName.text = castPhotosArray[indexPath.row].name
return cell
}
}
extension CastCell: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.castID = castPhotosArray[indexPath.row].id
performSegue(withIdentifier: detailToPeopleSegueIdentifier, sender: self) //Use of unresolved identifier 'performSegue' error
}
}
extension CastCell {
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let peopleVC = segue.destination as! PeopleDetailViewController
peopleVC.id = self.castID
}
}
The problem is there is no option to perform the segue on the cell
There is no such thing as a "segue on a cell". A segue is from one view controller to another. performSegue is a UIViewController method. So you cannot say performSegue from within your CastCell class, because that means self.performSegue, and self is a UITableViewCell — which has no performSegue method.
The solution, therefore, is to get yourself a reference to the view controller that controls this scene, and call performSegue on that.
In a situation like yours, the way I like to get this reference is by walking up the responder chain. Thus:
var r : UIResponder! = self
repeat { r = r.next } while !(r is UIViewController)
(r as! UIViewController).performSegue(
withIdentifier: detailToPeopleSegueIdentifier, sender: self)
1: A clean method is to create a delegate protocol inside your UITableViewCell class and set the UIViewController as the responder.
2: Once UICollectionViewCell gets tapped, handle the taps inside the UITableViewCell and forward the tap to your UIViewController responder through delegatation.
3: Inside your UIViewController, you can act on the tap and perform/push/present whatever you want from there.
You want your UIViewController to know what is happening, and not call push/presents from "invisible" subclasses that should not handle those methods.
This way, you can also use the delegate protocol for future and other methods that you need to forward to your UIViewController if needed, clean and easy.
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)
}
I have an app with two view controllers... ViewController and CollectionViewController. I have filter functions in the viewController that perform color filters on an image loaded into this view controller. The CollectionViewController contains a collection view that acts as a horizontal scrolling menu with cells, that when pressed, are supposed to call the filter functions in ViewController. The storyboard has a show segue with ID "FilterSegue"
I got it to work with Notification Centers but I wanted to try to get it to work with protocols and delegates to learn about the methods. I received some suggestions, but have been unable to get the delegate to set despite numerous attempts.
Here is the code:
ViewController:
import UIKit
class ViewController: UIViewController, FilterDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "FilterSegue" {
let destvc = segue.destinationViewController as! CollectionViewController
destvc.filterDelegate = self
}
}
func onRedFilter() {
// some code
}
func onGreenFilter() {
// some code
}
func onBlueFilter() {
// some code
}
func onUnfiltered() {
// some code
}
}
CollectionViewController:
import UIKit
//Protocols for filter functions called by the filter menu collection view custom cells.
protocol FilterDelegate: class {
func onRedFilter()
func onGreenFilter()
func onBlueFilter()
func onUnfiltered()
}
class CollectionViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate {
let reuseIdentifier = "FilterCell"
var filterDelegate: FilterDelegate? = nil
#IBOutlet var collectionView: UICollectionView!
// Filter labels for custom filter menu cells.
var tableData:[String] = ["Red Filter",
"Green Filter",
"Blue Filter",
"Unfilter",
"New Filter 1",
"New Filter 2"]
// Filter images for custom filter menu cells.
var tableImages: [String] = ["waterfallred.png",
"waterfallgreen.png",
"waterfallblue.png",
"waterfall.png",
"waterfall.png",
"waterfall.png"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Set up collectionView for the filters.
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: 120, height: 80)
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collectionView!.registerClass(colvwCell.self, forCellWithReuseIdentifier: reuseIdentifier)
collectionView!.backgroundColor = UIColor.whiteColor()
self.view.addSubview(collectionView!)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// Set uo required methods for collection view.
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return tableData.count
}
// Method for custom collection view cell texts and images.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: colvwCell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! colvwCell
cell.backgroundColor = UIColor.grayColor()
cell.lblCell.text = tableData[indexPath.row]
cell.imgCell.image = UIImage(named: tableImages[indexPath.row])
return cell
}
// Method for calling functions upon pressing custom filter menu collection view cells. In this case, the filter functions in the main view controller are called using notifications.
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
print("Cell \(indexPath.row) selected")
guard let filterDelegate = filterDelegate else {
print("Filter delegate wasn't set!")
return
}
switch indexPath.row {
case 0:
filterDelegate.onRedFilter()
case 1:
filterDelegate.onGreenFilter()
case 2:
filterDelegate.onBlueFilter()
case 3:
filterDelegate.onUnfiltered()
default:
print("No available filter.")
}
}
}
The guard statement returns "Filter delegate wasn't set!" on any depressed cell on the collection view menu.
instead of this
protocol FilterDelegate: class {
func onRedFilter()
----
}
try this
protocol FilterDelegate {
func onRedFilter()
----
}
I found the answer...
I realized that the segue wasn't being set because it was trying to instantiate another collection view controller with the segue. I removed the prepareForSegue altogether and the segue from the storyboard.
I have a button called onFilter2 on the main view controller that instantiates the collection view in a container on a press (calls a function showFilterMenu to show the filter menu) and then hides it on a second press (hideFilterMenu). The showFilterMenu function is where the colectionviewcontroller is instantiated. So, I set the delegate in this function. It took one line of code.
Here is the function executed upon pressing the filter button with the correct call of the delegate.
// New onFilter button action that shows/hides the filter collection view scroll bar on main view controller.
#IBAction func onFilter2(sender: UIButton) {
if filterButtonOn == true {
if (sender.selected) {
hideFilterMenu()
editButton.selected = false
sender.selected = false
} else {
showFilterMenu()
sender.selected = true
hideSliderMenu()
editButton.selected = false
//Reset color sliders to initial values.
redHorizontalSlider?.setValue(0, animated: false)
greenHorizontalSlider?.setValue(0.0, animated: false)
blueHorizontalSlider?.setValue(0.0, animated: false)
// Set initial values for color indicators.
redSliderIndicator.text = "0.00"
greenSliderIndicator.text = "0.00"
blueSliderIndicator.text = "0.00"
}
}
}
// Shows the filter menu scrolling bar in container view upon request.
func showFilterMenu() {
let newViewController = self.storyboard?.instantiateViewControllerWithIdentifier("FilterCollectionView") as! CollectionViewController
newViewController.view.translatesAutoresizingMaskIntoConstraints = false
self.toggleOnViewController(newViewController)
self.currentViewController = newViewController
// Set the delegate for the filter functions called by the cells in the collectionViewController filter menu.
// Set the delegate for the filter functions called by the cells in the collectionViewController filter menu.
//Here is the line of code that fixes the problem...
newViewController.filterDelegate = self
filterMenuShowOn = true
}
// Hides filter menu scrolling bar from container view upon request.
func hideFilterMenu() {
currentViewController!.view.alpha = 1
UIView.animateWithDuration(0.5, animations: {
self.currentViewController!.view.alpha = 0
},
completion: { finished in
self.currentViewController!.view.removeFromSuperview()
self.currentViewController!.removeFromParentViewController()
})
}
I want to thank Matt for getting me thinking about what was happening.
I have a UICollectionView and want to be able to perform custom behaviour when the user scrolls through implementing the scrollView delegate methods. Is it possible to have two separate objects that act as the collectionView delegate and scrollView delegate when working with a collectionView?
You cannot have separate delegates. UICollectionView is a subclass of UIScrollView, and overrides its delegate property to change its type to UICollectionViewDelegate (which is a subtype of UIScrollViewDelegate). So you can only assign one delegate to a collection view, and it may implement any combination of UICollectionViewDelegate methods and UIScrollViewDelegate methods.
However, you can forward the UIScrollViewDelegate methods to another object without much difficulty. Here's how you'd do it in Swift; it would be very similar in Objective-C (since this is all done using the Objective-C runtime):
import UIKit
import ObjectiveC
class ViewController: UICollectionViewController {
let scrollViewDelegate = MyScrollViewDelegate()
override func respondsToSelector(aSelector: Selector) -> Bool {
if protocol_getMethodDescription(UIScrollViewDelegate.self, aSelector, false, true).types != nil || protocol_getMethodDescription(UIScrollViewDelegate.self, aSelector, true, true).types != nil {
return scrollViewDelegate.respondsToSelector(aSelector)
} else {
return super.respondsToSelector(aSelector)
}
}
override func forwardingTargetForSelector(aSelector: Selector) -> AnyObject? {
if protocol_getMethodDescription(UIScrollViewDelegate.self, aSelector, false, true).types != nil || protocol_getMethodDescription(UIScrollViewDelegate.self, aSelector, true, true).types != nil {
return scrollViewDelegate
} else {
return nil
}
}
Note that MyScrollViewDelegate probably has to be a subclass of NSObject for this to work.
If I understand you correctly, then you just need your view controller to subclass UICollectionViewController or UICollectionViewDelegate. Then you can access the scrollView delegate methods since they are inherited by the collectionView
Create subclass of UICollectionViewController and write scroll view delegates into it.
class CustomCollectionViewController: UICollectionViewController {
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
}
}
In your target class
class MyCollectionViewController: CustomCollectionViewController, UICollectionViewDelegateFlowLayout {
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
return cell
}
}