unwind from tap in cell collectionView - ios

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

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.

Passing data from CollectionView to DetailVC in Swift 4

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

collectionView function didSelectItemAt indexPath to pass data to next view controller

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.

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 a selected collectionview cell to another view controller (ios)?

I'm being able to detect the selected row (image) in my collection view, But I need to send it to another view controller. Here is a part of the code :
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? CollectionViewCell {
cell.cellImage.image = UIImage(named: images[indexPath.row])
return cell
} else {
return CollectionViewCell()
}
}
//Printinig the selected image ID in console
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
SelectedItem = indexPath.row + 1
print(SelectedItem)
}
//Navigate to MPViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let DestViewController = segue.destination as! MPViewController
DestViewController.labelText = String(SelectedItem)
}
}
Initialize a variable first
var imageToPass: UIImage!
Then update didSelectItemAt func
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
SelectedItem = indexPath.row + 1
print(SelectedItem)
self.imageToPass = UIImage(named: images[SelectedItem])
performSegue(withIdentifier: "TargetVC", sender: imageToPass) //here you give the identifier of target ViewController
}
Go to your TargetVC and initialize a variable
var getImage: UIImage!
Then override the function in previous VC
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "TargetVC" {
if let targetVC = segue.destination as? TargetVC {
if let imageToPass = sender as? UIImage {
TargetVC.getImage = imageToPass
}
}
}
}
//Printinig the selected image ID in console
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
self.SelectedItem = indexPath.row + 1
self.selectedImage = UIImage(named: images[indexPath.row]);
print(SelectedItem)
}
//Navigate to MPViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let DestViewController = segue.destination as! MPViewController
DestViewController.imageSelected = self.selectedImage;
DestViewController.selectedItem = String(self.SelectedItem);
}
Now in MPViewController you can use the data self.imageSelected and self.selectedItem as per your requirements.
Take one instance variable in your destination class and set value of it in prepare for segue and then in viewDidload set that string to your label's text like,
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let DestViewController = segue.destination as! MPViewController
DestViewController.yourText = String(SelectedItem)
}
ans in viewDidload
yourLabel.text = yourText

Resources