segue when clicking on a uicollectionview cell inside a table view cell - ios

I have the following situation:
ExploreViewController is a table view controller with CategoryTableViewCell (child class of UITableViewCell)
The CategoryTableViewCell has a CollectionView with UICollectionViewCells.
When a user clicks on a UICollectionViewCell, I would like to segue ExploreViewController to BookListViewController based on the uICollectionViewCell data. Each UICollectionViewCell stands for a separated category.
How can I do this using swift? I tried using delegate protocol, but when I call performSegue inside the delegate function inside the ExploreViewController, I get an error.
In CategoryTableViewCell
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("test12")
self.delegate?.didSelectInEmbeddedCollection()
}
}
protocol CategoryTableViewCellDelegate {
func didSelectInEmbeddedCollection()
}
In ExploreViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("test14")
if segue.identifier == "toBookListFeatured",
let destination = segue.destination as? BookListViewController,
let selectedIndexPath = self.tableViewFeaturedCats.indexPathForSelectedRow,
let selectedCell = self.self.tableViewFeaturedCats.cellForRow(at: selectedIndexPath) as? CategoryTableViewCell
{
let collectionView = selectedCell.collectionView
let indexPathC = collectionView?.indexPathsForSelectedItems?.first
let cell = collectionView?.cellForItem(at: indexPathC!) as? CategoryCollectionViewCell
destination.data = cell!.label.text
} else {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
let dc = segue.destination as! BookListViewController
dc.data = self.tableRowData["name"] as! String;
dc.searchByCategory = true
}
}
}
extension ExploreViewController: CategoryTableViewCellDelegate {
func didSelectInEmbeddedCollection() {
performSegue(withIdentifier: "toBookListFeatured", sender: nil)
}
}

Related

Swift - Issue accessing data from UICollectionView embedded in UITableView

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.

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.

How to parse data from a UIViewController having 2 CollectionViews with swift

How do I parse data from a UIViewController having 2 different UICollectionViews?
I have 2 UICollectionViews in on UIViewController I have been able to pull the data from the server to the views and its displayed but i'm having trouble parsing it to the next screens.
I have tired this using didSelectItemAt and performSegue but it can't parse the data to the other screen
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == self.categoryView {
let categoryViewController = self.storyboard?.instantiateViewController(withIdentifier: "CategoryListViewController") as! CategoryListViewController
categoryViewController.self.selectedC = self.category[indexPath.row]
self.navigationController?.pushViewController(categoryViewController, animated: true)
} else if collectionView == self.featuredView {
self.selectedPro = self.property[indexPath.row]
self.performSegue(withIdentifier: "showDet", sender: self)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showCat"{
let categoryController = segue.destination as! CategoryListViewController
categoryController.selectedC = self.selectedC
}
else {
let detailViewController = segue.destination as! DetailsViewController
detailViewController.selectedPro = self.selectedPro
}
}
I'm expected to parse data to the second screen
Why are you using two different collection views? Why don't you just create two different cells? That way you can easily do an if let in the didSelect which type of cell has been tapped and pass the data on?
This Is the Solution
#IBOutlet weak var categoryView: UICollectionView!
let collectionViewAIdentifier = "catCell"
var category = [Category]()
#IBOutlet weak var featuredView: UICollectionView!
let collectionViewBIdentifier = "feaCell"
var property = [Property]()
override func viewDidLoad() {
super.viewDidLoad()
SVProgressHUD.show(withStatus: "Loading...")
categoryView.delegate = self
categoryView.dataSource = self
self.view.addSubview(categoryView)
featuredView.delegate = self
featuredView.dataSource = self
self.view.addSubview(featuredView)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
var count:Int?
// Category Collection View
if collectionView == self.categoryView {count = category.count}
//Property Collection View
if collectionView == self.featuredView {count = property.count}
return count!
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// Category Collection View
if collectionView == self.categoryView {
let cellA = collectionView.dequeueReusableCell(withReuseIdentifier: collectionViewAIdentifier, for: indexPath) as! HomeCollectionViewCell
let cat: Category
cat = category[indexPath.row]
cellA.homId.text = cat.idd
cellA.homName.text = cat.name
//idCat = cat.idd!
return cellA
}
//Property Collection View
else {
let cellB = collectionView.dequeueReusableCell(withReuseIdentifier: collectionViewBIdentifier, for: indexPath) as! FeaturedCollectionViewCell
let pro: Property
pro = property[indexPath.row]
cellB.proId.text = pro.idd
cellB.proName.text = pro.name
return cellB
}
}
//Parsing data to next screen
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Category Collection View parsing data
if segue.identifier == "showCat" {
let categoryDetailVC = segue.destination as! CategoryListViewController
let cell = sender as! UICollectionViewCell
let indexPath = self.categoryView.indexPath(for: cell)
let categoryId = category[(indexPath?.row)!].idd
categoryDetailVC.idString = categoryId
}
//Property Collection View parsing data
else if segue.identifier == "showDet" {
let detailVC = segue.destination as! DetailsViewController
let cell = sender as! UICollectionViewCell
let indexPath = self.featuredView.indexPath(for: cell)
let detailId = property[(indexPath?.row)!].idd
detailVC.idProString = detailId
}
}

Passing data from a collection view cell button to another view controller (Swift)

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)
}
}

How to Send Collection View Cell Text via Segue

I am trying to send a label that is contained within a collection view cell to another view controller with a segue.
My plan is that when a user taps on the collection view cell, the app then segues to the next view controller where the navigation bar's title displays the text of the label in the collection view cell selected.
I have tried this:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CCCollectionViewCell
//itemSelected = items[indexPath.row] as String
itemSelected = cell.pLabel.text!
print(itemSelected)
}
and in prepareForSegue I have not written any code as I am not sure how this works.
I commented out the block '..items[indexPath.row] as String' because it won't show the label and added the print function to see what will output but it only outputs the name given in the storyboard.
I am very new to Xcode so am not familiar with didSelect and prepareForSegue. All I am trying to do is to send the text within a collection view cell to another view controller with a segue.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "contentVideoSegue", sender: indexPath)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "contentVideoSegue"{
let selectedIndexPath = sender as? NSIndexPath
let videoContentVC = segue.destination as! VideoContentController
videoContentVC.text = items[selectedIndexPath.row] as String
}
}
in the hope of helping :)
From your code you are not calling the performSegue(withIdentifier:sender:) so you probably have created segue from the CollectionViewCell to DestinationViewController. So get the indexPath using this cell in prepareForSegue method.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let cell = sender as? UICollectionViewCell,
let indexPath = self.collectionView.indexPath(for: cell) {
let vc = segue.destination as! SecondViewController //Cast with your DestinationController
//Now simply set the title property of vc
vc.title = items[indexPath.row] as String
}
}
So you don't need to set up the cell in didSelect because you're already doing that in cellForItemAtIndexPath.
Rather you'll want to call performSegue(withIdentifier: "SegueName", sender: indexPath) in your didSelectItemAtIndexPath. Then in your prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let indexPath = sender as? IndexPath else { return }
let collectionCell = collectionView.cellForItem(at: indexPath)
let textToPass = collectionCell.textLabel.text
let detailVC = segue.destination as? DetailViewController
detailVC.passedInString = textToPass
}

Resources