For the life of me I cannot figure out why I can't get this function to work in response to an indexPath being selected.
When I add a breakpoint to the line in didSelectItemAtIndexPath it breaks but when I add one where it function is in the viewscontroller class it won't break. It's as if the information is being sent but cannot reach its destination. I get no error in my console and I've looked over everything multiple times.
Could this be an xcode bug or have I missed something?
Here is where I reference the class it's located in
var viewsController: ViewsController?
Here's my didSelectItemAtIndexPath
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
viewsController?.scrollToMenuIndex(indexPath.item)
print(indexPath.item)
}
And here is the function itself
func scrollToMenuIndex(_ menuIndex: Int) {
let indexPath = IndexPath(item: menuIndex, section: 0)
collectionView?.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition(), animated: true)
}
Your viewsController is declared but not initialized. So it is unknown which class method is called.
var viewsController: ViewsController?
if scrollToMenuIndex is in same class just scrollToMenuIndex(indexPath.item) would be enough !! If its not in same view controller, you need to initialize viewsController as
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
let viewsController= storyboard.instantiateViewController(withIdentifier: "someViewController")
viewsController?.scrollToMenuIndex(indexPath.item)
print(indexPath.item)
}
Hope it helps. Happy Coding!!
The reference while valid was returning nil for viewsController.
So in the closure that referenced the collection view that housed the cells that I was using for my didSelectItemAtIndexPath (located in viewsController),
I had to add c.viewsController = self and that fixed it.
I'll have to take a look at what the documentation says about this. I'll update my answer then if no one has yet to explain this behavior.
Related
I've been driven insane for hours as I can't get around with the issue.
I have a collection view which can have different section with different no. of items in each. For each section I need to use a section header of different type. So for this, I'm going to use UICollectionReusableView. But I can't seem to succeed in using a custom subclass of UICollectionReusableView by means of UINib registration.
A crash happens when I downcast the reusable view to my subclass. Like:
let friendHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind,
withReuseIdentifier: "FriendHeaderView",
for: indexPath) as! FriendHeaderView
Below is the code snippet:
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
private let viewModel = ProfileViewModel()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
// more code
collectionView.register(UINib(nibName: "FriendHeaderView", bundle: nil),
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: "FriendHeaderView")
}
}
Now here is the data source implementation:
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
// valid implementation
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// valid implementation
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// valid implementation
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionView.elementKindSectionHeader:
let friendHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "FriendHeaderView", for: indexPath) as! FriendHeaderView
// *** Crash happens here *** //
return friendHeader
default:
assert(false, "Invalid element type")
}
}
}
And I don't know why the collectionView(_:layout:referenceSizeForHeaderInSection:) needs to be also implemented. So here it is:
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
let size = CGSize(width: collectionView.bounds.width, height: 100)
return size
}
}
Okay, now come to the point: The above mentioned crash doesn't happen
at all if I don't downcast with as! operator. Well, if I use section
header from the storyboard instead of UINib registration, there is
no crash.
If I'm going to need multiple type header, then I can't also use storyboard approach or without down-casting approach as I need to feed data to those headers.
What can I do to have multiple type headers with view built from interface builder?
I've made a demo project with what I've said above. If anyone is interested please check out that.
Once you assign proper class and identifier in your Xib file, then it will work without crashes.
Well, after some more investigation and the input from #good4pc in the accepted answer (actually I found out that by myself before looking at the answer) it seems that the issue is actually happening for some unwanted issue with Xcode.
When we create any view (preferably, UITableViewCell or UICollectionViewCell) with .xib, the class identity is provided automatically for that .xib in the identity inspector. But this was not the case for UICollectionReusableView. See the attached screenshot below for easy understanding.
This is a UICollectionViewCell subclassed with .xib:
This is a UICollectionReusableView subclassed with .xib:
So the key is to provide the class identity of the .xib file which
is done from the attributes inspector.
first of all, i did set the delegates. every other protocol is working with collection views.
I tested it with the sizeFotItem function. and that worked just fine. But how come that cellForItem function does not respond at all?
Anyone know why?
here is my code;
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.delegate = self
self.collectionView.dataSource = self
let flow = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layoutSettings(flow)
playButtonPressed(self)
}
And this is in my extension of my viewController;
extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 9
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as! CollectionCell
print(menu.menuArray[indexPath.row])
cell.cellText.text = menu.menuArray[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Do something")
}
}
The funny part is.. I have almost the same code in an other project, and every is working fine.
Only difference is that the project has an collectionView INSIDE a TableView.
Hopefully one of you find an obvious reason why this is not working. I'm gladly to know why :)
thanks.
PS. Is it normal that the editor doesn't recognize the "extension ViewController" part as code?
Solved by just deleting the CollectionViewCell and making a new one.
I still don't know what was wrong with my previous collectionViewCell, because every protocol was working just fine except for didSelectItemAt..
If anybody had a familiar problem and knows what was going on, I'm still happy to hear what was going on ;)
I am writing a code for my school project. As a beginner would, I looked up on tutorials on youtube how to code.
To summarize the hierarchy of my used objects:
Main collectionView (first one loaded) for scrolling horizontally between views.
List collectionView for listing cells.
collectionViewCell cells for listing information.
However, I am unable to find a way so that when I call a didSelectItemAt function by tapping on one of the cells to push a new view. I have tried creating a function that pushes the view in the MainViewController and call it by creating an instance of the class in didSelectItemAt function but I had no luck.
have you added datasource and delegate?. if you added please check cell click working or not. if click is working add bellow navigation code.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let controller = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
self.navigationController?.pushViewController(controller, animated: true)
}
Contributing to Raj's answer, if you want to push a view controller when selecting some specific item in your collection view, just a slight variation of condition will do the trick, this delegate method is called with you click on an item in your collection view:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if indexPath.item == **<your item's index>** {
let controller = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
self.navigationController?.pushViewController(controller, animated: true)
}
}
Add the upper Side the Class like this :
class tableViewVC : UIViewController, UITableViewDelegate, UITableViewDataSource {
}
Add inside ViewDidLoad():
tableView.delegate = self
tableView.datasource = self
And then you will use in TableView Method like this :
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let ViewController = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController
self.navigationController?.pushViewController(ViewController!, animated: true)
}
I have a collectionView. I have its dataSource and delegate methods set and I call func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { } when the user first makes a selection which works fine.
If the user leaves the view controller and comes back to it I call the below methods in viewWillAppear to programmatically force cell selection and scroll back to the cell that was initially chosen.
let whateverCellWasChosen = 29
let indexPath = IndexPath(item: whateverCellWasChosen, section: 0)
collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false)
collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredHorizontally)
// problem is here
collectionView(self.collectionView, didSelectItemAt: indexPath)
But when trying to call the manual version of didSelectItem I keep getting an error of:
Cannot call value of non-function type 'UICollectionView'
The error stems from the first argument that accepts the collectionView. Why is it giving me a problem?
Use
collectionView.delegate?.collectionView!(collectionView, didSelectItemAt: indexPath)
I have more than one collectionView in a ViewController. The cell of those collectionViews has the same format.. so I'm reusing them. So my question is: How to identify in the method
func collectionView(_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath)
I don't want to do a couple of if's
I've found this solution everywhere, but really don't like it. Here is the code
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
if let aCell = cell as? ItemCollectionViewCell{
aCell.setupCell(with: self.items[indexPath.item])
}
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == self.colletionViewTwo{
// goto viewController1
}else if collectionView == self.colletionViewOne{
// goto viewController2
}
}
Create two classes that implement the collection view delegate and data source and use one of each. So you'll have these two extra objects in your current view controller.
Seeing your code now, the above is probably too heavy. Alternatively, add a dictionary in which you store the collection view as key and a selector as value. This is extensible as you say you want.
To be honest, what's your issue an if (or switch) statement like you have now?