I have a tableView, with a prototype cell containing a UICollectionView. I’ve setup the tableView according to this tutorial (https://medium.com/#stasost/ios-how-to-build-a-table-view-with-multiple-cell-types-2df91a206429), and the UI is working. I can pass data through my tableView and into the collectionView.
View Layout
When a collectionViewCell is selected it segues to another view.
I haven’t figured out how to access the data from the collectionViewCell and pass it to the new view.
The collectionView is initialized within the tableView prototype cell. I've tried didSelectRow -> prepareForSegue (code below), but the commands do not autocomplete, and are not working.
Here's the code for the tableViewCell, where the collectionView is setup.
EDIT: Removed commented code for clarity
import UIKit
class homeFeedTableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var feedCollectionView: UICollectionView!
var selectedEvent : Event?
var collectionItems = [CollectionViewModelItem]()
var collectionItem : CollectionViewModelItem?
#IBOutlet weak var sectionHeadingLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
feedCollectionView.delegate = self
feedCollectionView.dataSource = self
print("collection items \(collectionItems.count)")
for item in collectionItems{print("type: \(item.type), title: \(item.eventTitle)")}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
// setup view model
var item: TableViewModelItem? {
didSet {
// if not right class, skip
guard let item = item as? TableViewModelFeed else {
return
}
sectionHeadingLabel.text = item.sectionTitle
}
}
// create reuse identifier property
static var identifier: String {
return String(describing: self)
}
}
import Foundation
import UIKit
extension homeFeedTableViewCell {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// print("dataCount3: \(collectionItems.count) \(collectionItems[collectionItems.count-1].type)")
return collectionItems.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// let cell = UICollectionViewCell()
// return cell
self.collectionItem = collectionItems[indexPath.row]
switch collectionItem!.type {
case .yourEvents:
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier:YourEventsCollectionViewCell.identifier, for: indexPath) as? YourEventsCollectionViewCell{
cell.item = collectionItem
print(cell.item?.type)
print(".yourEvents")
return cell
}
case .feed:
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: mainFeedCollectionViewCell.identifier, for: indexPath) as? mainFeedCollectionViewCell{
cell.item = collectionItem
print(".feed")
return cell
}
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("\(collectionItems[indexPath.row].eventTitle) tapped")
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourEventsToEventViewController" || segue.identifier == "feedToEventViewController"{
print("prepare for segue1")
let destinationVC = segue.destination as! EventViewController
if collectionItem != nil{
print("prepare for segue2")
destinationVC.backgroundImageUrl = collectionItem!.backgroundImageUrl
}
}
}
}
}
A UICollectionView keeps track of its selected indexPaths with the property indexPathsForSelectedItems. Since you trigger your segue in collectionView(didSelectItem: atIndexPath:), your selected indexPath is available during prepare(forSegue:). You could try the following:
class MyViewController: UIViewController, UICollectionViewDelegate {
...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
performSegue(withIdentifier: "mySegue", sender: self)
}
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
guard let destinationVC = segue.destination as! EventViewController,
segue.identifier == "mySegue" else { return }
// In this context, your selected cell is the one who fired the segue
if let selectedIndexPaths = collectionView.indexPathsForSelectedItems,
let firstSelectedIndexPath = selectedIndexPaths.first {
let selectedObject = collectionItems[firstSelectedIndexPath.row]
destinationVC?.backgroundUrl = selectedObject.backgroundUrl
}
}
}
The sequence is:
You select a cell (through user interaction, ie tapping).
didSelect performs a segue named "mySegue" (in this example).
In prepareForSegue, you look for your selected index paths. Assuming you aren't using multi-selection, you want your first and only indexPath. Using that index path, you can retrieve your data in your collectionItems array.
Related
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.
My CollectionView should pass a class model to DetailViewController, but when I tap on a cell I get the nil error.
Fatal error: Unexpectedly found nil while implicitly unwrapping an
Optional value
The CollectionViewController is embedded programmatically on a TabBarController.
Collection View
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return soundArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SoundCell", for: indexPath) as? SoundCell {
let SoundClass = soundArray[indexPath.row]
cell.updateUI(SoundClass: SoundClass)
return cell
} else {
return UICollectionViewCell()
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "seguetosound", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "seguetosound" {
if let detailVC = segue.destination as? DetailSecondVC
let sound = sender as? SoundClass {
detailVC.SoundClass = sound
}
}
Detail View Controller
class DetailSecondVC: UIViewController, UIWebViewDelegate {
private var _SoundClass: SoundClass!
var SoundClass: SoundClass {
get {
return _SoundClass
} set {
_SoundClass = newValue
}
}
Do you know what I am missing here? I tested the segue with a simple white screen and it works but when I try to pass the data, it fails.
The correct approach is this. First, figure out how you want to trigger the segue. One option is, in didSelect, trigger the segue in code:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "seguetosound", sender: self)
}
But even better, just delete didSelectItemAt completely and have the segue in the storyboard come from the cell. That way the segue is triggered automatically when the user taps the cell.
Then, in prepare, find out what index path was selected, and pull out the data from the model and pass it to the destination view controller (this might not compile, because your variable names are so atrocious that I can't read your code, but it is the correct approach generally):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "DetailSecondVC" {
if let detailVC = segue.destination as? DetailSecondVC {
if let paths = collectionView?.indexPathsForSelectedItems {
let row = paths[0].row
detailVC.SoundClass = SoundClasss[row]
}
}
}
}
Edited: I thought the solution was to make the segue from the view controller instead of from the cell, but as matt said, the segue was correct from the cell but I just had to remove the implementation of didSelectItemAt
I've been trying for hours but I do not understand how I hope someone can help me!
In practice I have an initial VC I click on an image that takes me to a collectionView through a follow and the 2 elements are also joined by a NC.
In the CollectionView are inserted images, which are contained in an array, I would like to touch on an image to return to the initial VC and that the image displayed is the one selected in the CollectionView.
I tried with UnWind but I can not carry the information of the image index that I try to recover in the didselct.
Viewcontroller
class ViewController: UIViewController {
#IBOutlet weak var immagine: UIImageView!
#IBAction func actionTap(_ sender: UITapGestureRecognizer) {
print("tap")
performSegue(withIdentifier: "selezione", sender: nil)
}
#IBAction func indietro(segue: UIStoryboardSegue){
let recupero = segue.source as! CollectionViewController
print(recupero.indice)
immagine.image = UIImage(named: recupero.arrayImmagini[recupero.indice])
}
private let reuseIdentifier = "Cell"
...
}
CollectionViewController
class CollectionViewController: UICollectionViewController {
var arrayImmagini = ["globe","bed","home","toolbox"]
var indice = 0
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arrayImmagini.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! CollectionViewCell
cell.imgCell.image = UIImage(named: arrayImmagini[indexPath.row])
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// print(arrayImmagini[indexPath.row])
let indice = indexPath.row
// idImg = indexPath.row
}
...
}
even if set index = indexPath.row it is never recovered in the unwind
You have wired the unwind segue to your collection view cell. The unwind happens before didSelectItemAt runs.
You can fix this in one of two ways:
Remove the segue from the cell, and wire it from the viewController icon (top left icon) to the exit icon (top right icon). Find this exit segue in the Document Outline and give it an identifier such as "returnToMain". In didSelectItemAt, call self.performSegue(withIdentifier: "returnToMain", sender: self) after setting indice.
OR
Don't use didSelectItemAt at all. Instead, implement prepare(for:sender:):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "returnToMain" {
if let indexPath = self.collectionView.indexPathsForSelectedItems?.first {
indice = indexPath.row
}
}
}
You should implement some handler for this purpose or delegate method. For example:
class ViewController: UIViewController {
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourCollectionViewController" {
if let vc = segue.destination as? CollectionViewController {
vc.imageHandler = { imageIndex in
...
}
}
}
}
}
class CollectionViewController: UICollectionViewController {
var imageHandler : ((_ imageId: Int) -> Void)?
...
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// print(arrayImmagini[indexPath.row])
let indice = indexPath.row
// idImg = indexPath.row
imageHandler?(indice)
dismiss(animated: true, completion: nil)
}
}
I want my collectionView that's housed in AllWorkoutsVC to pass videoCode string data to the next viewController that's named VideoViewVC.
My collection view presents data correctly, however is the didSelectItemAt func i am having trouble with... this code runs fine and no warnings or errors are thrown, just that the variable myPassVideoCode not being passed to the target view controller
(i have removed some code for clarity..please ask for more if you feel is needed.)
collectionView class
class AllWorkoutsVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
var myPassVideoCode = String()
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "WorkoutsCell", for: indexPath) as? WorkoutsCell {
let workout = workouts[indexPath.row]
cell.updateViews(workout: workout)
print("this is my workouts: " + workout.videoCode)
return cell
} else {
return WorkoutsCell()
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let chosenWorkout = DataService.instance.getAllWorkouts()[indexPath.row]
myPassVideoCode = String(chosenWorkout.videoCode)
print("this is my: " + myPassVideoCode)//Note: this whole print does not appear in the console either
performSegue(withIdentifier: "onPlayPressed2", sender: chosenWorkout)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "onPlayPressed2" {
let toNextVC = segue.destination as! VideoViewVC
toNextVC.myPassVideoCode = myPassVideoCode
}
}
}
Target Class...
class VideoViewVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
var myPassVideoCode = String()
#IBOutlet var videoPlayer: YouTubePlayerView!
override func viewDidLoad() {
super.viewDidLoad()
videoPlayer.loadVideoID(myPassVideoCode)
}
The issue is that you're declaring a new local constant with the same name myPassVideoCode in:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt...
You only need to set myPassVideoCode. So change this line:
let myPassVideoCode = String(chosenWorkout.videoCode)
to:
myPassVideoCode = String(chosenWorkout.videoCode)
You are making a new constant inside didSelectItemAt:
let myPassVideoCode = ...
So you are not storing the value to the variable myPassVideoCode that you declared in the beginning of the class definition.
I have a button inside a collection view cell and when pressed I want to go to another view controller and pass a string to that view controller. The only problem I'm having is with passing the data, I don't know how to check from which cell the button was clicked.
extension UserViewController: UICollectionViewDataSource{
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return self.posts.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PostCell", for: indexPath) as! UsersCollectionViewCell
//cell.post = posts[indexPath.item]
cell.User_Name.text = "\(self.posts[indexPath.item].firstname!) \(self.posts[indexPath.item].lastname!)"
cell.Country.text = self.posts[indexPath.item].Country
//user id is in the posts().uid
return cell
}
//the segue is already made in the storyboard, i am trying to pass the user id in the function below
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Testing1"{
var view = segue.destination as! ViewTheAccount
//user_ID = self.posts[indexPath.item].firstname
}
}
}
Add a string variable to you cell class and give it strValue that you want in cellForRow
// btn action in cell
#IBAction func btnClicked(_ sender: Any)
{
/// access cell and get it's string var , self = cell
/// here use delegate to push another view controller with self.strValue
}
Try this (I assume here that only single selection is allowed here, plus I am assuming that the segues are started by selecting a cell):
if segue.identifier == "Testing1" {
var view = segue.destination as! ViewTheAccount
if let itemIndex = collectionView.indexPathsForSelectedItems?.first?.item {
let selectedItem = self.posts[itemIndex]
// do here what you need
}
}
So one way to do this is to send the cell in the delegate call or callback that you are using.
class SomeViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
var collectionView:UICollectionView!
collectionView(method that dequeues the cell){
let yourcell = collection view.dequeue(...) as! SomeCell
yourcell.somecallback = callback
}
func callback(cell: UICollectionViewCell){
//To find out which cell it is just
let indexPath = collection view.indexPathForCell(cell)
//YOU NOW know which cell this was sent from.
}
}
class SomeCell: UICollectionViewCell{
var somecallback:((UICollectionViewCell)->())?
func didPress(sender: UIButton){
somecallback(self)
}
}