Passing a variable from a customCell.xib's button to another UIViewController - swift

My custom cell has a button that when clicked, the user can be taken to another ViewControler. That button has a titleLabel with the String of a user id. What I want to do is take the user to a new UIViewController, passing that clicked buttons's titleLabel (user id) to a variable on the new View Controller. That way, I can use that variable (user id) get further information from firebase and display it on the UI View controller.
on the .xib of the custom cell, I print the UID to make sure each button prints with the correspondent ID, which it does. I can't figure out a way to pass that ID to a new ViewController.
I tried researching online and I found out you can't do prepare(for segue) or performsegue(WithIdentifier) on a customCell.xib.
I tried doing delegates and then protocols but still couldn't get it to work. I am new with Swift. Any help would be great, thank you!
This is the customCell.Xib's Swift file:
class CustomCellTableViewCell: UITableViewCell {
#IBOutlet weak var postImage: UIImageView!
#IBOutlet weak var nameLbl: UILabel!
#IBOutlet weak var descriptionLbl: UITextView!
#IBOutlet weak var profileImageBtn: UIButton!
#IBOutlet weak var profileImage: UIImageView!
#IBOutlet weak var view: UIView!
var btnSelected : Bool = false
var vcInstance: ProfilesTableVC?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
view.clipsToBounds = true
view.layer.cornerRadius = view.frame.size.width / 2
profileImage.layer.cornerRadius = profileImage.frame.size.width / 2
descriptionLbl.alpha = 0.7
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
#IBAction func profileBtnPressed(_ sender: Any) {
let passValue = UserProfileVC()
let profileTvC = ProfilesTableVC()
print (profileImageBtn.titleLabel!.text!)
var id = (profileImageBtn.titleLabel!.text!)
profileTvC.didSelectProfileBtn(senderID: id)
}
This is the tableViewController, where I everything gets loaded (not where I want to pass the value). I tried passing the value here and then do a prepareForSegue to pass the value to the new UIViewController but the value becomes nil after the segue happens. I am just including code where the .xib call the function from the table view.
class ProfilesTableVC: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate,UINavigationControllerDelegate, UIImagePickerControllerDelegate{
let cell = CustomCellTableViewCell()
func didSelectProfileBtn(senderID: String) {
senderIDArray.append(senderID)
var index = senderIDArray.count - 1
selectedCellUserID = senderIDArray[index]
performSegue(withIdentifier: "showSendersProfile", sender: Any?.self)
}
This is the UIViewController where I want to pass the variable and display further information from Firebase using that ID
import UIKit
import Firebase
class UserProfileVC: UIViewController {
let customCell = CustomCellTableViewCell()
let profileTvC = ProfilesTableVC()
#IBOutlet weak var profileImage: UIImageView!
#IBOutlet weak var nameLbl: UILabel!
var delegate : Any?
var getName = String()
var getsenderID = String()
let userDataRef = Database.database().reference().child("Users")
override func viewDidLoad() {
super.viewDidLoad()
print("ID is: \(profileTvC.selectedCellUserID)")
let sender = getsenderID
print(sender)
}
}

If you don't want to use protocols, you can addTarget to the button in the tableView - cellForRowAt method, also in that method you set the tag to the row index value.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let profileCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! MoviewReviewCell
reviewCell.profileImageBtn.tag = indexPath.row
reviewCell.profileImageBtn.addTarget(self, action: #selector(tappedOnXibCellButton), for: .touchUpInside)
return reviewCell
}
#objc func tappedOnXibCellButton(sender: UIButton) {
print(sender.tag)
performSegue(withIdentifier: reviewListToDetails, sender: sender.tag)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let identifier = segue.identifier
if identifier == "segueName" {
let destViewController = segue.destination as! DestinationClassViewController
let selectedIndex = sender as! Int
let profileId = profilesList[selectedIndex].id
destViewController.profileId = profileId
}
}

You seem to create a NEW ProfilesTableVC, and try to perform the segue on. The newly created one is not on your view stack, and not from your storyboard.
You could add this while returned cellForRowAt (at the ProfilesTableVC of course):
cell.vcInstance = self
Then in the button click
#IBAction func profileBtnPressed(_ sender: Any) {
print (profileImageBtn.titleLabel!.text!)
var id = (profileImageBtn.titleLabel!.text!)
vcInstance?.didSelectProfileBtn(senderID: id)
}
You can do it with protocols/delegates. I think you tried but there is something wrong with your trial.
Define a callback delegate:
protocol CustomCellDelegate: AnyObject {
func customCell(didSelectButton name: String)
}
You will notice extending AnyObject. This is to allow weak references, try to read about Swift ARC
Then, in your cell:
weak var delegate: CustomCellDelegate?
Then in the profileBtnPressed(_ sender: Any)
#IBAction func profileBtnPressed(_ sender: Any) {
var id = (profileImageBtn.titleLabel!.text!)
delegate?.customCell(didSelectButton: id)
}
When dequeing the cell:
(cell as? CustomCellTableViewCell)?.delegate = self

Related

passing CoreData from UITableViewCell to viewcontroller

I'm trying to create the feature where when a user clicks on a specific cell in my UITableView, the Project will segue to a new ViewController and show all the information that has been saved in the CoreData. The Problem is that when I touch on a cell I get an error of
Unexpected nil while unwrapping optional value
Here is my code as it is right now within the ViewController that has the TableView
class ContactViewController: UIViewController, UITableViewDataSource, NSFetchedResultsControllerDelegate, UITableViewDelegate {
var selectName:String?
var selectPhone:String?
var selectComment:String?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectName = contact[indexPath.row].name
selectPhone = contact[indexPath.row].phone
selectComment = contact[indexPath.row].comments
performSegue(withIdentifier: "MySegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MySegue"{
if let destVC = segue.destination as? IndiViewController {
destVC.NameLabel = selectName
destVC.PhoneLabel = selectPhone
destVC.CommentLabel = selectComment
}
}
}
This is my code in IndiViewController (the VC in which I want the user to view the contact)
class IndiViewController: UIViewController {
#IBOutlet var NameLabel: UILabel!
#IBOutlet var PhoneLabel: UILabel!
#IBOutlet var CommentsLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
}
I've tried a few other methods but all still delivered the same error
I troubleshooted a bit to see which variable truly was causing the nil by doing this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MySegue"{
if let destVC = segue.destination as? IndiViewController {
destVC.NameLabel = "selectName" //I thought if perhaps the nil was the var selectName this would at least let me know
}
}
However even through this, the app crashes and gives the same error. So I think the issue is with the Labels in the IndiViewController.
So I tried creating an empty String and assigning it to NameLabel like this:
class IndiViewController: UIViewController {
#IBOutlet var NameLabel: UILabel!
var name = ""
override func viewDidLoad() {
super.viewDidLoad()
NameLabel.text = name
}
}
but still no luck.
What am I doing wrong?
Yet it can be an IBOutlet error, even for typo.
delete #IBOutlet var NameLabel: UILabel!
remove the link in the StoryBoard "referencing outlets" tab
click-drag a new outlet for the UILabel and do not name it with a capital letter
// #IBOutlet var NameLabel: UILabel!
#IBOutlet var nameLabel: UILabel!
Edit
Also try to pass your informations in two times :
create a new var in your destinationViewController like :
class IndiViewController: UIViewController {
#IBOutlet var nameLabel: UILabel!
var myText: String = ""
override func viewDidLoad() {
super.viewDidLoad()
nameLabel.text = myText // assign the var to the labels text
}
}
In your prepareForSegue method assign myText and not the nameLabel.text
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MySegue"{
if let destVC = segue.destination as? IndiViewController {
destVC.myText = "selectName"
}
}
}
my guess is that you cant acces IBOutlets in prepare(for segue:) method. The destination ViewController is not built yet

How to update UITableView when a CustomCell label value changes?

I have a custom cell class with two buttons and one label defined in its own class. Am using protocol-delegates to update the value of the label when the button is pressed. But am not able to figure out how to update the UITableView.
protocol customCellDelegate: class {
func didTapDecrementBtn(_ sender: AllCountersTableViewCell)
func didTapIncrementBtn(_ sender: AllCountersTableViewCell)
func updateTableViewCell(_ sender: AllCountersTableViewCell)
}
class AllCountersTableViewCell: UITableViewCell {
#IBOutlet weak var counterValueLbl: UILabel!
#IBOutlet weak var counterNameLbl: UILabel!
#IBOutlet weak var counterResetDateLbl: UILabel!
#IBOutlet weak var counterDecrementBtn: UIButton!
#IBOutlet weak var counterIncrementBtn: UIButton!
weak var delegate: customCellDelegate?
#IBAction func decrementBtnPressed(_ sender: UIButton) {
delegate?.didTapDecrementBtn(self)
delegate?.updateTableViewCell(self)
}
#IBAction func incrementBtnPressed(_ sender: UIButton) {
delegate?.didTapIncrementBtn(self)
delegate?.updateTableViewCell(self)
}
In my ViewController, I have provided the delegate. But reloadData() is not working, so although sender.counterLbl.value is changing its not reflecting on the view.
extension AllCountersVC: customCellDelegate {
func didTapIncrementBtn(_ sender: AllCountersTableViewCell) {
guard let tappedCellIndexPath = tableView.indexPath(for: sender) else {return}
let rowNoOfCustomCell = tappedCellIndexPath[1]
let newValue = String(allCounter[rowNoOfCustomCell].incrementCounterValue(by: allCounter[rowNoOfCustomCell].counterIncrementValue))
sender.counterValueLbl.text = newValue
}
func updateTableViewCell(_ sender: AllCountersTableViewCell) {
allCountersTableView.reloadData()
}
In cellForRow you must set cell.delegate = self for this logic to start working. Its not enough just to make you controller confroms to your custom cell delegate protocol. From your post I assume delegate is always nil in the cell, that is why it does not work.

Parsing data from a collectionview Cell to a new viewController using a button in the CollectionViewCell

I have a collectionView that I am populating using an array I created in a class. The collectionView works but the problem is when I want to segue to another viewController using a button in the collectionViewCell as well as parsing specific data from that cell to the viewController.
An example of what I am trying to do is if I press the button in the cell for the question image I want to segue to the next viewController and I want that viewController's image to be the question image, and maybe i the future add some other data corresponding to it.
I have looked at the other answers on this site and other sites, which got me this far, but there is nothing that I can find to fix this problem for a collectionView. I know that it is similar to a tableView, but when I try and change it based on a tableView, but it doesn't seem to work for me.
This is the image of what the first and second views look like
This is the class i created:
import Foundation
import UIKit
class Information {
var image = ""
var button = ""
var infoOne = ""
var infoTwo = ""
var price = ""
init(image: String, button: String, infoOne: String, infoTwo: String, price: String) {
self.image = image
self.button = button
self.infoOne = infoOne
self.infoTwo = infoTwo
self.price = price
}
//MARK: Carousel Data
static func createData() -> [Information] {
return [
Information(image: "question", button: "Buy One", infoOne: "First Data", infoTwo: "Second Data", price: "10"),
Information(image: "exclamation", button: "Buy Two", infoOne: "First Data", infoTwo: "Second Data", price: "20"),
Information(image: "period", button: "Buy Three", infoOne: "First Data", infoTwo: "Second Data", price: "30"),
Information(image: "all", button: "Buy Four", infoOne: "First Data", infoTwo: "Second Data", price: "40")
]
}
}
This array works just fine when it populates the collectionViewCell.
This is the collectionViewCell
import UIKit
class InfoCollectionViewCell: UICollectionViewCell {
var infomation: Information! {
didSet {
updateUI()
}
}
var addBtnAction : (()-> ())?
#IBOutlet weak var infoImage: UIImageView!
#IBOutlet weak var infoLblOne: UILabel!
#IBOutlet weak var infoLblTwo: UILabel!
#IBOutlet weak var priceLbl: UILabel!
#IBOutlet weak var buyBtn: UIButton!
fileprivate func updateUI() {
infoImage.image! = UIImage(named: infomation.image)!
infoLblOne.text = infomation.infoOne
infoLblTwo.text = infomation.infoTwo
priceLbl.text = infomation.price
buyBtn.setTitle(infomation.button, for: .normal)
}
#IBAction func buyBtnPressed(_ sender: Any) {
print("INFORMATION: \(infomation.image)")
addBtnAction?()
}
override func layoutSubviews() {
super.layoutSubviews()
self.layer.cornerRadius = 5.0
self.clipsToBounds = true
}
}
this is the viewController:
import UIKit
class ViewController: UIViewController {
let collectionViewCellId: String = "carouselCollectionCell"
#IBOutlet weak var collectionView: UICollectionView!
fileprivate var information = Information.createData()
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.delegate = self
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return information.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "infoCell", for: indexPath) as! InfoCollectionViewCell
cell.addBtnAction = {
self.performSegue(withIdentifier: "mainSegue", sender: self)
}
cell.infomation = self.information[indexPath.item]
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mainSegue" {
let nxtVC = segue.destination as? DetailViewController
let cell = sender as! InfoCollectionViewCell
let myIndexPath = self.collectionView.indexPath(for: cell)
nxtVC?.infomation = information[(myIndexPath?.item)!]
}
}
}
So when I run the app everything is fine until I press the button to segue and parse data. The app crashes and this is what it is saying is the cause:
This is what is in the output console:
Could not cast value of type 'carousel.ViewController' (0x107992178) to 'carousel.InfoCollectionViewCell' (0x107991f98).
2018-09-28 21:22:52.116698-0400 carousel[1573:38999] Could not cast value of type 'carousel.ViewController' (0x107992178) to 'carousel.InfoCollectionViewCell' (0x107991f98).
(lldb)
I would prefer to keep the button if at all possible, but if it is not possible I will change that. I am not sure what it is that I am doing wrong. If there is anything else that I can help with please let me know. Thank you very much.
EDIT:
It seems that I forgot to include one of the important views, the one for the other viewController that is segued to.
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
this is the view that i segue to:
import UIKit
class DetailViewController: UIViewController {
#IBOutlet weak var infoImage: UIImageView!
#IBOutlet weak var dataTxtView: UITextView!
var testingImage: UIImage = UIImage()
var infomation: Information! {
didSet {
updateUI()
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
fileprivate func updateUI() {
let images = UIImage(named: infomation.image)
infoImage.image = images
print("Image: \(infomation.image)")
}
}
In the updateUI function I tried to make the infoImage the same as infomation.image but it crashes and states:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
This doesn't make much sense to me because I tried to print what the string of infomation.image is, and it worked, but the moment I try and put it as an image it seems to crash.
Thank you again
EDIT2:
I figured out what I was doing wrong. It seems that I was always trying to use an optional string. How i fixed it is that I changed the class I made and instead of a string and turn that into an image it is already an image. I also tried to create an if let statement to remove the optional.
This is what the prepareForSegue looks like in the main ViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mainSegue" {
let nxtVC = segue.destination as? DetailViewController
let cell = sender as? InfoCollectionViewCell
let myIndexPath = self.collectionView.indexPath(for: cell!)
if let indexPath = myIndexPath {
nxtVC?.infomation = information[indexPath.row]
}
}
}
this is what the viewDidLoad looks like:
override func viewDidLoad() {
super.viewDidLoad()
print("***********TESTINGIMAGE: \(infomation.image!)***********")
infoImage.image = infomation.image!
}
I know that I am unwrapping the image with an ! but i don't know how to remove the optional otherwise.
Thank you for the Help.
The problem is that you are assuming that the sender in prepare is an instance of the cell but your call to performSegue is passing self as the sender. But self in that case is the view controller, not the cell.
The solution is to pass the cell instead of self.
cell.addBtnAction = {
self.performSegue(withIdentifier: "mainSegue", sender: cell)
}

Populating UI with info from tableview cell? - Swift

Hi I have tried to follow tutorials online but can't seem to get it working.
I am trying to populate a view controller with information from the pressed cell from a table view ... it picks up which cell has been selected but then opens the view controller twice... it also doesn't populate the ui label?
Please advise thank you.
Table View Class where user selects a cell ;
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let indexPath = tableView.indexPathForSelectedRow!
let currentCell = tableView.cellForRow(at: indexPath)! as UITableViewCell
title = currentCell.textLabel?.text
performSegue(withIdentifier: "passInfoSegue", sender: self)
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?){
if (segue.identifier == "passInfoSegue") {
// initialize new view controller and cast it as your view controller
let viewController = segue.destination as! NextViewController
// your new view controller should have property that will store passed value
viewController.passedValue = title!
}
}
Next View Controller ;
class NextViewController : UIViewController{
var passedValue : String!
#IBOutlet weak var featLabel: UITextView!
#IBOutlet weak var descripLabel: UITextView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var ampImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
featLabel.text = passedValue
}
}

Transfer TextField data between ViewControllers

I have ViewController, which has a textfield and a button. The user enters his name into the textfield and hits the DONE button. When he hits the button, he is segued to GifteeDetails, which is a different view controller. There is a label in that viewController that is supposed to display his name. But, his name doesn't show up. I don't receive an error.
Here's ViewController:
#IBOutlet weak var textGifteeName: UITextField!
#IBAction func toDetails(_ sender: Any) {
performSegue(withIdentifier: "toDetails", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destDetails: GifteeDetails = segue.destination as! GifteeDetails
destDetails.nametext = textGifteeName.text!
destDetails.agetext = "\(Int(age)! - 2000 + 17)"
destDetails.locationstext = labelGifteeLocationsPreview.text!
destDetails.intereststext = labelGifteeInterestsPreview.text!
}
Here's GifteeDetails:
var nametext = String()
var agetext = String()
var locationstext = String()
var intereststext = String()
#IBOutlet weak var labelGifteeName: UILabel!
#IBOutlet weak var labelGifteeAge: UILabel!
#IBOutlet weak var labelGifteeLocations: UILabel!
#IBOutlet weak var labelGifteeInterests: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
nametext = labelGifteeName.text!
agetext = labelGifteeAge.text!
locationstext = labelGifteeLocations.text!
intereststext = labelGifteeInterests.text!
}
Sorry about all the !. Swift gives me an error unless I have them.
You are updating the strings nametext and others, which are not connected to your labels.
You need to replace this piece of code:
destDetails.nametext = textGifteeName.text!
destDetails.agetext = "\(Int(age)! - 2000 + 17)"
destDetails.locationstext = labelGifteeLocationsPreview.text!
destDetails.intereststext = labelGifteeInterestsPreview.text!
With:
destDetails.labelGifteeName.text = textGifteeName.text!
destDetails.labelGifteeAge.text = "\(Int(age)! - 2000 + 17)"
destDetails.labelGifteeLocations.text = labelGifteeLocationsPreview.text!
destDetails. labelGifteeInterests.text = labelGifteeInterestsPreview.text!
nametext is a String object, and it is different from labelGifteeName.text which is the String of the label you want to update.
If you use segue then pls remove it, and then create action method of that button in the FirstviewController and use pushViewController to move on the SecondviewController.
Swift 3
Example:
#IBAction func toDetails(_ sender: Any)
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "SecondView") as! SecondView
UserDefaults.standard.set(self. textGifteeName.text, forKey: "savedStringKey")
UserDefaults.standard.synchronize()
self.navigationController?.pushViewController(vc,
animated: true)
}
In SecondView:
override func viewDidLoad() {
super.viewDidLoad()
self.labelGifteeName.text = UserDefaults.standard.string(forKey: "savedStringKey")!
}
Please Update your func viewDidLoad() method in GifteeDetailsVC with labelGifteeName.text! = nametext , labelGifteeAge.text!= agetext instead of your code because you have already assigned the value in to strings i.e, nametext and agetext in ViewController, you need to display string value in label