So I have a TableViewController with a CollectionView and a custom Cell, but I want to prepare a segue from that CollectionView to a TableView.
I tried this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "drawSegue" {
let dst = segue.destination as! DrawerViewController
guard let indexPath = cupCollectionView.indexPathsForSelectedItems else{
return
}
dst.cup = cupboards[indexPath.row] as! Cupboard
}
}
But I get an error Value of type '[IndexPath]' has no member 'row'. Is there another way to do that? Cause in another project I used this from a tableView to a tableView and it worked...
This is my CollectionView:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cupboards.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cupCell", for: indexPath) as! CupboardCollectionViewCell
let cupboard = cupboards[indexPath.row]
cell.cupLabel.text = cupboard.name
return cell
}
Thank you in advance
It looks like cupCollectionView.indexPathsForSelectedItems is returning an array of IndexPath objects, so instead of accessing a single IndexPath.row you're trying to call Array<IndexPath>.row which doesn't exist
In your didSelectCell when you call the performSegue, add your cell as the sender, then you can change it to let indexPath = cupCollectionView.indexPath(for: sender)
Related
I have here my collection view controller and when I click on the collection view cell it is updating between true and false but my label is not updating from added to not added. any help would be greatly appreciated I have been at this for 4 days. I have tried reading the apple documentation and it doesn't give examples as to how to accomplish this with using a boolean value. What I want to happen is for the Label atop my collection view cell label to change from added to not added when the collection view cell has been pressed. I know it is working because when I tested it with print statements when I press the collection view cell it changes back and forth from true to false. I am just having trouble with the label changing from added to not added with it.
import UIKit
class ShoppingListCollectionViewController: UICollectionViewController {
var shoppingItemController = ShoppingItemController()
var shoppingItemCollectionViewCell = ShoppingItemCollectionViewCell()
override func viewDidLoad() {
super.viewDidLoad()
shoppingItemCollectionViewCell.updateViews()
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShoppingListDetailSegue" {
guard let shoppingListDetailVC = segue.destination as? ShoppingListDetailViewController else {
return
}
}
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
// MARK: UICollectionViewDataSource
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return shoppingItemController.shoppingItems.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ShoppingItemCell", for: indexPath) as? ShoppingItemCollectionViewCell else {
fatalError("Collection view cell identifier is wrong or the cell is not a ShoppingItemCollectionViewCell")
}
// Configure the cell
let shoppingListItem = shoppingItemController.shoppingItems[indexPath.item]
cell.imageView.image = shoppingListItem.image
cell.shoppingItemLabel.text = shoppingListItem.imageName
if shoppingListItem.added == true {
cell.hasBeenAddedLabel.text = "Added"
} else {
cell.hasBeenAddedLabel.text = "Not Added"
}
return cell
}
// MARK: UICollectionViewDelegate
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
var chosenItem = shoppingItemController.shoppingItems[indexPath.item]
chosenItem.added = !chosenItem.added
shoppingItemController.shoppingItems[indexPath.item] = chosenItem
if chosenItem.added == true {
chosenItem.updateViews()
}
print(chosenItem.added)
}
You only need to get the cell from cellForItem(at:) method inside the didSelectItemAt method and update the label text to your desired string:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! ShoppingItemCollectionViewCell
var chosenItem = shoppingItemController.shoppingItems[indexPath.item]
chosenItem.added.toggle()
cell.label.text = chosenItem.added ? "Added" : "Not Added"
}
I want to make it so that when I tap on a CollectionView Cell, it segues to another view. I also want to pass a user ID to this view so I can query the database. This is what I have implemented so far:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionview.dequeueReusableCell(withReuseIdentifier: "userCell", for: indexPath) as! UserCell
print(user[indexPath.row].imagePath!)
cell.userImage.sd_setImage(with: URL(string: user[indexPath.row].imagePath!))
cell.nameLabel.text = user[indexPath.row].username
cell.userID = user[indexPath.row].userID
let destinationVC = ProfileViewController()
destinationVC.sentUserID = user[indexPath.row].userID!
// Let's assume that the segue name is called playerSegue
// This will perform the segue and pre-load the variable for you to use
//destinationVC.performSegue(withIdentifier: "toProfileFromSearch", sender: self)
cell.addButtonTapAction = {
// implement your logic here, e.g. call preformSegue()
self.performSegue(withIdentifier: "toProfileFromSearch", sender: self)
}
//cell.userImage.downloadImage(from: self.user[indexPath.row].imagePath!)
//checkFollowing(indexPath: indexPath)
return cell
}
This is in the individual creation of each cell. When I click the user profile in the search area, nothing happens.
I followed other stack overflow questions to get this far. Can anyone make a complete solution for other people that need this too?
You can use the didSelectItemAt function, check this
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let VC1 = self.storyboard!.instantiateViewController(withIdentifier: "IDYOURVIEW") as! ProfileViewController
VC1.sentUserID = user[indexPath.row].userID!
self.navigationController?.pushViewController(VC1, animated: true)
}
If you're using segues
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "toProfileFromSearch", sender: user[indexPath.row].userID!)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "toProfileFromSearch"
{
let vc = segue.destination as? ProfileViewController
if let id = sender as! String
{
vc?.sentUserID = id
}
}
}
There is another better way to handle this using protocol
protocol UserCellTapedDelegate: AnyObject {
func didTapButton(_ cell: UserCell, didSelectItemAt id: Int)
}
then your cell would be like:
class UserCell: UICollectionViewCell {
public var userID: Int!
public weak var delegate: UserCellTapedDelegate?
#objc private func addButtonTapAction() {
delegate?.didTapButton(self, didSelectItemAt: userID)
}
}
userID: to store the user ID.
delegate: use an optional weak (in terms of memory management) delegate reference in your cell.
addButtonTapAction func to handle target of the button/or tap gesture on Any UIView.
The next step is to go back to your UIViewController and conform to the custom delegate:
extension ViewController: UserCellTapedDelegate {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionview.dequeueReusableCell(withReuseIdentifier: "userCell", for: indexPath) as! UserCell
...
cell.userID = user[indexPath.row].userID!
cell.delegate = self
return cell
}
func didTapButton(_ cell: UserCell, didSelectItemAt id: Int) {
self.performSegue(withIdentifier: "toProfileFromSearch", sender: id)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toProfileFromSearch" {
let vc = segue.destination as! ProfileViewController
if let id = sender as! String {
vc.sentUserID = id
}
}
}
}
Hope this helps you 😊
So I have a main ViewController that has Horizontal Scrolling CollectionViews inside a TableView. When you click on an Item in the CollectionView it transfers all the data from the Item to a new ViewController to display it properly.
The problem I am facing is when you click on the item in the CollectionView a segue is performed that transfers data to an array in the new ViewController but instead of transferring only data from the particular item it send data from more than one item. On some debugging I also found that the items Custom class is what loads the multiple elements in the array and not the segue. Here is some code.
cellForRowAt in main VC
if let cell = tableView.dequeueReusableCell(withIdentifier: "HeadlineRow", for: indexPath) as? HeadlineRow {
cell.latest = self.latest
cell.collectionView.reloadData()
cell.delegate = self
return cell
}
Segue Code in main VC
func pushToNewsVC(latestNews: [LatestNews]) {
self.transferToNewsVC = latestNews
performSegue(withIdentifier: "NewsVC", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "NewsVC" {
if let destination = segue.destination as? NewsVC {
destination.latestNews.append(contentsOf: self.transferToNewsVC)
destination.SuggestedLatestNews.append(contentsOf: self.latest)
}
}
}
Headlinerow which is TableViewCell class
var latest = [LatestNews]()
var transferData = [LatestNews]()
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return latest.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HeadlineCell", for: indexPath) as? HeadlineCell {
let latest = self.latest[indexPath.row]
cell.updateUI(latest: latest)
return cell
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? HeadlineCell {
self.transferData.removeAll()
self.transferData = cell.transferData
delegate?.pushToNewsVC(latestNews: self.transferData)
}
}
HeadlineCell which is CollectioViewCell class
var transferData = [LatestNews]()
func updateUI(latest: LatestNews) {
storyTextView.text = latest.headline
self.thumbnail.sd_setImage(with: URL(string: "\(latest.app_img)"))
self.transferData.append(latest)
}
I have this code to make a UICollectionView with in another ViewController:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return tvSeries.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let identifier = "Item"
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as! SeriesCollectionViewCell
cell.itemLabel.text = tvSeries[indexPath.row]
cell.itemImage.image = UIImage.init(imageLiteralResourceName: tvSeries[indexPath.row])
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let item = sender as? UICollectionViewCell
let indexPath = collectionView.indexPath(for: item!)
let detailVC = segue.destination as! DetailViewController
detailVC.detailName = tvSeries[(indexPath?.row)!]
}
I want to have a ViewController with 2 CollectionViews like in this picture:
In your collectionView functions such as numberOfItemsInSection, check if the collectionView argument is equal to one or the other collection view and then return the number as required.
For example, if you had the following IBOutlets:
#IBOutlet weak var collectionViewOne: UICollectionView!
#IBOutlet weak var collectionViewTwo: UICollectionView!
then your numberOfItemsInSection would change to:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == collectionViewOne {
return itemSource1.count
}
else {
return itemSource2.count
}
}
Do the same for the rest of the collectionView functions.
I created a UICollectionView with this tutorial.
And then I want pass indexPath from this UICollectionView to my GameViewController like in this question/answer.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: LevelCell = collectionView.dequeueReusableCellWithReuseIdentifier("Level", forIndexPath: indexPath) as! LevelCell
cell.imgCell.image = UIImage(named: levels[indexPath.row + 1]!.levelImage)
cell.lblCell.text = levels[indexPath.row + 1]!.levelLabel
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "StartGame" {
if let gameView = segue.destinationViewController as? GameViewController {
if let cell = sender as? UICollectionViewCell, indexPath = collectionView.indexPathForCell(cell) {
gameView.level = indexPath
}
}
}
}
But this does not work - even when I use LevelCell instead of UICollectionViewCell. I get this error:
Cannot invoke 'indexPathForCell' with an argument list of type '(UICollectionViewCell)'
How can I get the indexPath of the element I just touched in my UICollectionView so that I can bypass it from this function to GameViewController?
You're casting the senderto UICollectionView, but it's probably UICollectionViewCell.
if let cell = sender as? UICollectionViewCell, indexPath = collectionView.indexPathForCell(cell) {
gameView.level = indexPath
}