Trouble Passing Variable Info to First VC Using Closure - swift

I am trying to pass the data of which name was selected by the user in my ShipViewController to my ProfileViewController. I tried using closures to do so, but the title of the button in ProfileViewController (which presents the modal popover to ShipViewController) isn't changing to the name the user selects in ShipViewController.
Should it not be String --> () or is the way I instantiate my view controller incorrect?
(ShipViewController)
var completionHandler:((String) -> ())?
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "shipCell", for: indexPath) as! ShipViewCell
if selectedIndex == indexPath.row {
let result = completionHandler?(shipNames[selectedIndex!])
self.dismiss(animated: true, completion: nil)
}
}
(In viewDidLoad of ProfileViewController)
let vc = storyboard?.instantiateViewController(withIdentifier: "ShipViewController") as! ShipViewController
vc.completionHandler = { (text) -> ()in
print(text)
self.shipButton.setTitle(text, for: .normal)
}

Dismiss ShipViewController in didSelectItemAt
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let result = completionHandler?(shipNames[indexPath.item])
self.dismiss(animated: true, completion: nil)
}
In ProfileViewController Don't assign to completionHandler in viewDidLoad
Assign to completion handler in prepare for segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showShip" {
if let vc = segue.destination as? ShipViewController {
vc.completionHandler = { (text) -> ()in
print(text)
self.shipButton.setTitle(text, for: .normal)
}
}
}
}

Related

Swift: having trouble getting segue to show details from selecting item in a view collection

I am trying to set a View Collection to segue to a item's details when the item's image is clicked on. Here is the code I have. However, when I try, I get the error, Thread 1: Fatal error: Index out of range, next to this line of code: destinationController.shop = shopping[indexPaths[0].row]
Here is my full code for the collection view controller. Can anyone tell me what I am doing wrong?
import UIKit
private let reuseIdentifier = "Cell"
class shopCollectionViewController: UICollectionViewController {
#IBAction func unwindToMain(segue: UIStoryboardSegue){
}
var shopping: [Shop] = [Shop(image:"blueTshirt", name:"blueTshirt", price: 10), Shop(image:"blackTshirt", name:"blackTshirt", price: 10), Shop(image:"lightblueTshirt", name:"lightblueTshirt", price: 10), Shop(image:"whiteTshirt", name:"whiteTshirt", price: 10)]
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Do any additional setup after loading the view.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return shopping.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "dataCell", for: indexPath) as! shopCollectionViewCell
// Configure the cell
let item = shopping[indexPath.row]
cell.itemImage.image = UIImage(named: item.image)
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
performSegue(withIdentifier: "showDetail", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPaths = collectionView.indexPathsForSelectedItems{
let destinationController = segue.destination as! itemDetailsViewController
destinationController.shop = shopping[indexPaths[0].row]
collectionView.deselectItem(at: indexPaths[0], animated: false)
}
}
When you use collectionView maybe you should use indexPath.item instead of indexPath.row
A more reliable way than indexPathsForSelectedItems is to pass the index path while calling performSegue.
And deselect the cell right inside didSelectItemAt
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
performSegue(withIdentifier: "showDetail", sender: indexPath)
collectionView.deselectItem(at: indexPath, animated: false)
}
And replace prepare(for with
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
let indexPath = sender as! IndexPath
let destinationController = segue.destination as! itemDetailsViewController
destinationController.shop = shopping[indexPath.item]
}
}

Why does my like button like all my images when I only liked one image?

I have this collection view with all these images in my VC. When I tap on one of them another VC opens up and I can tap on a button to like the image which is indicated by the button changing its image. The problem Im having is that all the other images are getting changed too. How would I specifically just like the one image I tapped on and not all of them. Here is the code Im working with:
//First VC
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath) as!
CustomCollectionViewCell
//shows text
cell.nameLbl.text = heroes[indexPath.row].localized_name.capitalized
cell.nameLbl.adjustsFontSizeToFitWidth = true
//shows images
let defaultLink = "https://api.opendota.com"
let completeLink = defaultLink + heroes[indexPath.row].img
print("This is my\(completeLink)")
cell.imageView.downloaded(from: completeLink)
cell.imageView.isUserInteractionEnabled = true
//shows the num of how many heroes there are
let heroCount = heroes.count
howManyHerosLabel.text = String(describing: heroCount)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let completeLink = defaultLink + heroes[indexPath.item].img//this will return the exact string for selected cell
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let secondViewController = storyboard.instantiateViewController(withIdentifier: "secondViewController") as! HeroViewController
secondViewController.completeLink2 = completeLink
secondViewController.completeLink3 = heroes[indexPath.item].localized_name.capitalized
self.present(secondViewController, animated: true, completion: nil)
print("the title of the image is \(completeLink)")
}
//Second VC
override func viewDidLoad() {
super.viewDidLoad()
if UserDefaults.standard.bool(forKey: "Change") {
likedButton.setImage(UIImage(named: "selected_star"), for: .normal)
}
//returns image and text that was presssed on in the VC
heroImageView2.downloaded(from: completeLink2)
labelForHero!.text = completeLink3
}
#IBAction func addToLikes(_ sender: Any) {
//progressView.isHidden = false
UserDefaults.standard.set(true, forKey: "Change")
likedButton.setImage(UIImage(named: "selected_star"), for: .normal)
}
You need to have unique values to identify if its liked or unliked
UserDefaults.standard.set(true, forKey: "Change")
For now .. you are checking against change ... once set to true will remain true ...
My suggestion is to pass heroes[indexPath.item].img to other controller and set UserDefault against this value

Perform a segue from a UICollectionViewCell to show a detail screen not working

I have a ViewController that contains a UICollectionView where each cell is a custom cell. I need to perform a segue, so when the user taps over any of the cell, a new ViewController is shown for showing the detail of the pressed cell.
With the code I have right now, the segue is performed if I press over a cell with two fingers, but not with just one. Moreover, when the DetailsViewController is show, the title is not updated.
I cannot post images, but the segue I create in the storyboard goes from the cell to the DetailsViewController, is type Show (e.g. Push) and the id is showDetail.
ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(UINib.init(nibName: "MovieCell", bundle: nil), forCellWithReuseIdentifier: "MovieCell")
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIInputViewController.dismissKeyboard))
view.addGestureRecognizer(tap)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
performSegue(withIdentifier: "showDetail", sender: indexPath)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "showDetail") {
guard let viewController = segue.destination as? DetailsViewController else {return}
guard let indexPath = sender as? IndexPath else {return}
viewController.movie = self.moviesList[indexPath.row]
}
}
DetailsViewController.swift
class DetailsViewController: UIViewController {
var movie: Movie? = nil {
didSet {
navigationController?.title = movie?.title
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
Any idea what is going wrong?
Change sender to the collectionView cell:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
performSegue(withIdentifier: "showDetail", sender: MovieCell)
}
Use this in prepare for segue:
if(segue.identifier == "showDetail") {
guard let viewController = segue.destination as? DetailsViewController else {return}
let cell = sender as MovieCell
guard let indexPath = self.collectionView!.indexPathForCell(cell) else {return}
viewController.movie = self.moviesList[indexPath.row]
}

Segue from CollectionViewCell

I have used this segue many times in swift, but I cannot make it work from a collectionViewCell. Why????
I have control + drag from the cell to a ViewController
Here is my code
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
performSegue(withIdentifier: "followSegue", sender: self.postsFollow[indexPath.item].follower)
print(self.postsFollow[indexPath.item].follower)
print("You selected cell #\(indexPath.item)!")
}
//Segue to show username in FindUserViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier! == "followSegue" {
let guest = segue.destination as! FindUserViewController
guest.pickedUser = sender as! String
let backItem = UIBarButtonItem()
backItem.title = "Back"
navigationItem.backBarButtonItem = backItem
}
}

UICollectionView cell is nil

I'm working on a collection view that gets populated with images that I have on Firebase. Everything works fine, but when I try to perform segue I get "unexpectedly found nil while unwrapping an Optional value" for this line:
if let indexPath = self.collectionView?.indexPath(for: sender as! UICollectionViewCell){}
I have seen many working examples here in SO and apparently they all work fine with that line.
Here is the rest of the relevant code:
//grab Firebase objects in viewdidload and put them into productsArray
var productsArray = [ProductsModel]()
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return productsArray.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
let imageView = cell.viewWithTag(1) as! UIImageView
//imageView.image = imageArray[indexPath.row]
let getUrl = productsArray[indexPath.row].productImg
imageView.loadUsingCache(getUrl!)
imageView.layer.borderColor = UIColor.lightGray.cgColor
imageView.layer.borderWidth = 1
imageView.layer.cornerRadius = 0
return cell
}
//NAVIGATION
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
performSegue(withIdentifier: "itemSegue", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "itemSegue"){
let destinationController = segue.destination as! ItemViewController
if let indexPath = self.collectionView?.indexPath(for: sender as! UICollectionViewCell){
destinationController.getProduct = productsArray[indexPath.row]
}
}
}
Also, I double checked everything is connected and set in the storyboard.
Thanks in advance!
In the following function, you send the sender parameter as nil:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
performSegue(withIdentifier: "itemSegue", sender: nil)
}
Then in the following function, you receive the sender parameter and try to cast it (sender as! UICollectionViewCell). This parameter will always be nil.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "itemSegue"){
let destinationController = segue.destination as! ItemViewController
if let indexPath = self.collectionView?.indexPath(for: sender as! UICollectionViewCell){
destinationController.getProduct = productsArray[indexPath.row]
}
}
}
If you don't want it to be nil, then don't invoke the performSegue(withIdentifier:sender:) function with a nil sender. Send a valid object. In this case, it seems you want sender to be of type UICollectionViewCell. So send the cell in the performSegue function.
Edit: Nirav D makes a good point in mentioning that there is no reason to send the cell since it just gets converted back into indexPath anyway. We could solve this entire issue by doing:
performSegue(withIdentifier: "itemSegue":, sender: indexPath)
and:
if let indexPath = sender as? IndexPath {
destinationController.getProduct = productsArray[indexPath.row]
}