UICollectionView Controller and Passing CloudKit data in Prepare for Segue - ios

I have successfully populated a UICollectionView controller with data and images from a CloudKit record, however I am having a problem passing the selected cell to the details UIViewController. Here is my code thus far -
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.staffArray.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> StaffCVCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! StaffCVCell
let staff: CKRecord = staffArray[indexPath.row]
let iconImage = staff.object(forKey: "staffIconImage") as? CKAsset
let iconData : NSData? = NSData(contentsOf:(iconImage?.fileURL)!)
let leaderNameCell = staff.value(forKey: "staffName") as? String
cell.leaderNameLabel?.text = leaderNameCell
cell.leaderImageView?.image = UIImage(data:iconData! as Data);
return cell
}
func prepare(for segue: UIStoryboardSegue, sender: StaffCVCell) {
if segue.identifier == "showStaffDetail" {
let destinationController = segue.destination as! StaffDetailsVC
if let indexPath = collectionView?.indexPath {
let staffLeader: CKRecord = staffArray[indexPath.row]
let staffID = staffLeader.recordID.recordName
destinationController.staffID = staffID
}
}
}
The problem occurs at the line -
let staffLeader: CKRecord = staffArray[indexPath.row]
where I am presented with the error -
Value of type '(UICollectionViewCell) -> IndexPath?' has no member
'row'
I have tried replacing row with cell, however this only presents another error -
Value of type '(UICollectionViewCell) -> IndexPath?' has no member
'cell'
I am sure there is something fundamental i'm missing but cannot see it. Any pointers greatly appreciated.

If your segue is triggered by touching a cell, you want something along the lines of code below:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showStaffDetail" {
let destinationController = segue.destination as! StaffDetailsVC
// Find the correct indexPath for the cell that triggered the segue
// And check that the sender is, in fact, a StaffCVCell
if let indexPath = collectionView?.indexPath(for: sender), let sender = sender as? StaffCVCell {
// Get your CKRecord information
let staffLeader: CKRecord = staffArray[indexPath.item]
let staffID = staffLeader.recordID.recordName
// Set any properties needed on your destination view controller
destinationController.staffID = staffID
}
}
}
Note that I've changed the method signature back to the standard method signature.

Related

Cannot perform segue from cell with indexPath

I am trying to perform a segue and pass data to detailViewController from my cell when tapped. I have tried with dummy data and I can pass them to detailScreen, however I would like to pass data that are related to cells.When I tapped a cell I get this error "Could not cast value of type ViewController to MovieCell" with line let selectedItem = ...
Thanks for your time and effort
Here is my code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "detailSegue"
{
let destination = segue.destination as! DetailViewController
let selectedItem = collectionView.indexPath(for: sender as! MovieCell)!
let character = characters[selectedItem.item]
destination.detailName = character.name
destination.detailGender = character.gender
destination.detailSpecies = character.species
destination.detailStatus = character.status
}
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
performSegue(withIdentifier: "detailSegue", sender: self)
}
try use:
collectionView.indexPathsForSelectedItems.first
instead
collectionView.indexPath(for: sender as! MovieCell)!
Also I wanna recommend for you my library for such situations: Link

Why is data not passed through my segue in my code?

I want to pass data via segue to the destination ViewController as a UICollectionViewCell is pressed using a protocol cellWasPressed().
The UICollectionView is embedded in a UITableViewCell hence making it the delegate for the collection view.
I have tried the following code to get it to do the task but unfortunately the event data is not being passed via the segue hence the destinantion ViewController is empty.
Here is the delegate code found in the UITableViewCell:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedIndex = indexPath
selectedIndex2 = self.indexPath
print(selectedIndex)
print(selectedIndex2)
delegate?.cellWasPressed()
}
protocol CellDelegate {
func cellWasPressed()
}
extension UIResponder {
func next<T: UIResponder>(_ type: T.Type) -> T? {
return next as? T ?? next?.next(type)
}
}
extension UITableViewCell {
var TableView: UITableView? {
return next(UITableView.self)
}
var indexPath: IndexPath? {
return TableView?.indexPath(for: self)
}
}
& here is the code to prepare for the segue in the BrowseViewController:
func cellWasPressed() {
if let index = selectedIndex{
self.performSegue(withIdentifier: "ViewEventDetails", sender: index)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "ViewEventDetails") {
let destination = segue.destination as? EventDetailViewController
if let index = sender as? IndexPath {
if let index2 = selectedIndex2 {
let cell = EventTable.cellForRow(at: index2) as! PopularCell
let events = groupedEventArray[index2.row].1
let eventToPass = events[index.row]
destination?.navigationItem.title = eventToPass.event_name
}
}
}
}
I wish to fix this code in other for it to pass the event data to the destination ViewController via the segue.

Passing data with override func prepare using collectionviewCell

I am trying to send the charName String from AvengersViewController to CharViewController.
I am using a collection view in AvengersViewController. CharName is a label in CharViewController.
What I am trying works perfectly with a table view but I can't get it to work using collectionViews...
I am using "lastItemSelectedA" to indicate the index of the label from my avengers array. The passing of data works... I can't get the index of the collectionViewItem to pass with the first segue, thus, making it null. By using the default value of 0 I have been able to notice that it does work, however, it does not change the value of lastItemSelectedA when the cell is pressed but after... Or at least it does not update the variable.
I have already tried at least 5 implementations from solutions on stack.
extension AvengersViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
lastItemSelectedA = indexPath.item
//self.performSegue(withIdentifier: "openToCharA", sender: indexPath.item)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let identifier = segue.identifier else { return }
switch identifier {
case "openToCharA":
if let destination = segue.destination as? CharViewController {
destination.charName = avengers[lastItemSelectedA ?? 0].name
}
//destination.sounds = sounds
//guard let indexPath = collectionView.indexPathsForSelectedItems else {return}
//let sounds = fallen[lastItemSelectedF!].sounds
default:
print("unexpected segue identifier")
}
}
If prepare(for segue is called then you've connected the segue from the collection view cell (rather than from the controller).
In this case delete
var lastItemSelectedA : Int
and
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
lastItemSelectedA = indexPath.item
//self.performSegue(withIdentifier: "openToCharA", sender: indexPath.item)
}
and get the index path of the collection view cell from the sender parameter
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "openToCharA" {
let cell = sender as! UICollectionViewCell
let indexPath = collectionView.indexPath(for: cell)!
let destination = segue.destination as! CharViewController
destination.charName = avengers[indexPath.item].name
}
}
Force unwrapping the optionals is fine in this case. The code must not crash if everything is hooked up correctly and if it does it reveals a design mistake.

a simple performSegue throws and error in prepare for segue func

Maybe something silly, but I am trying to simply move from a tableviewController to a viewController not passing any data just a simple move using
#IBAction func requestPanel(_ sender: Any) {
performSegue(withIdentifier: "sendMail", sender: AnyObject)
}
And then I get an error in a prepare for segue func.
But what does that have to no with my other segue?
I understand that that segue will have trouble since there is no data.
As you see here segue is above prepare for segue.
#IBAction func requestPanel(_ sender: Any) {
self.performSegue(withIdentifier: "sendMail", sender: AnyObject.self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var indexPath: IndexPath = self.tableView.indexPathForSelectedRow!
let desti = segue.destination as! DeviceDetailTableViewController
let selectedRecord = onlineDevices[indexPath.row]
let panelWidth = selectedRecord.object(forKey: "panelwidth") as? String
let panelHight = selectedRecord.object(forKey: "panelhight") as? String
let panelPitch = selectedRecord.object(forKey: "panelpitch") as? String
let panelPower = selectedRecord.object(forKey: "panelpower") as? String
let panelWeight = selectedRecord.object(forKey: "panelweight") as? String
let panelMaker = selectedRecord.object(forKey: "maker") as? String
let panelModel = selectedRecord.object(forKey: "model") as? String
desti.pawidth = panelWidth!
desti.pahight = panelHight!
desti.papitch = panelPitch!
desti.papower = panelPower!
desti.weight = panelWeight!
desti.maker = panelMaker!
desti.model = panelModel!
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return onlineDevices.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DeviceCell", for: indexPath)
// Configure the cell...
let noteRecord: CKRecord = onlineDevices[(indexPath as IndexPath).row]
cell.textLabel?.text = noteRecord.value(forKey: "maker") as? String
cell.detailTextLabel?.text = noteRecord.value(forKey: "model") as? String
return cell
}
}
All segues in a viewController will go through the same prepare(for:sender:) method. You need to use the segue.identifier to handle the different segues differently.
Your sendMail segue comes from a UIBarButton action instead of from a selected row, so check for that segue and handle it separately:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "sendMail" {
// nothing extra to be done here
} else {
// do your normal path here
if let indexPath = self.tableView.indexPathForSelectedRow {
// you have a valid selected row so proceed
}
}
}
The error message is clear. You are saying tableView.indexPathForSelectedRow at a time when no row is selected. Therefore that value is nil, and you crash when you force-unwrap it.
You wrote your original prepareForSegue when your only segue was triggered by the user selecting a row of the table. But this segue is not triggered by the user selecting a row of the table; it is triggered by the user tapping a button. You have to account for that difference.
(This is a major flaw in the whole architecture: all segues from a given view controller flow into the same prepareForSegue, which becomes a bottleneck.)

Segue from CollectionViewCell to Another View Controller Passing IndexPath

I'm trying to pass the indexPath of a cell tapped in a UICollectionView to another view controller. I can't seem to get the indexPath of what was selected and segue it to the next view controller
I get this error: "Could not cast value of type Post to PostCell"
View Controller #1:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let post = posts[indexPath.row]
if let cell = collectionView.dequeueReusableCellWithReuseIdentifier("PostCell", forIndexPath: indexPath) as? PostCell {
cell.configureCell(post)
}
return cell
}
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let selectedPost: Post!
selectedPost = posts[indexPath.row]
performSegueWithIdentifier("PostDetailVC", sender: selectedPost)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "PostDetailVC" {
//Error here: Could not cast value of Post to PostCell
if let selectedIndex = self.collection.indexPathForCell(sender as! PostCell){
print(selectedIndex)
}
if let detailsVC = segue.destinationViewController as? PostDetailVC {
if let selectedPost = sender as? Post {
print(selectedIndex)
detailsVC.post = selectedPost
detailsVC.myId = self.myId!
detailsVC.indexNum = selectedIndex
}
}
}
}
View Controller #2:
var indexNum: NSIndexPath!
override func viewDidLoad() {
super.viewDidLoad()
print(indexNum)
}
You are passing a Post instance which doesn't match the expected PostCell instance.
I recommend to pass the index path
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("PostDetailVC", sender: indexPath)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "PostDetailVC" {
guard let selectedIndexPath = sender as? NSIndexPath,
detailsVC = segue.destinationViewController as? PostDetailVC else { return }
print(selectedIndexPath)
let selectedPost = posts[selectedIndexPath.row]
detailsVC.post = selectedPost
detailsVC.myId = self.myId!
detailsVC.indexNum = selectedIndexPath
}
}
You are passing a Post and not a PostCell as sender. You don't need that anyway, as the collectionView keeps track of selected items.
Try this:
if let selectedIndex = self.collection.indexPathsForSelectedItems()?.first {
print(selectedIndex)
}
It's because you are giving argument which has type of Post and you are trying to cast it as PostCell. Which will not work.

Resources