Found Nil Using Protocol in TableViewCell - swift

I'm trying to update my model using data from a textField in a custom cell. I set up a protocol in the cell's class and send the info to my ViewController, however I continually get "Found nil while implicitly unwrapping an Optional value". What am I missing? Thanks!
protocol UpdateDelegate {
func didUpdate (someText: String)
}
class customTableViewCell: UITableViewCell {
var updateDelegate: UpdateDelegate!
#IBOutlet weak var someDescriptionField: UITextField!
#IBAction func someDescriptionField(_ sender: UITextField) {
updateDelegate.didUpdate(someText: sender.text ?? "") //error is here
}
}
extension ViewController : UpdateDelegate {
func didUpdate (someText: String) {
print(someText)
}
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, UIGestureRecognizerDelegate {
//....
viewDidLoad() {
self.tableView.delegate = self
self.tableView.dataSource = self
}
}
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! TableViewCell
cell.someDescriptionField.text = meal.arrayOfPossibleDishes[indexPath.section].arrayOfSteps[indexPath.row-1].stepName
cell.layer.cornerRadius = 10
return cell
}

Very simple answer here: You are never setting the delegate on your cell. Since your ViewController class conforms to your UpdateDelegate protocol you can update your cellForRow method like so:
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! TableViewCell
//set the delegate when setting up the cell
cell.updateDelegate = self
cell.someDescriptionField.text = meal.arrayOfPossibleDishes[indexPath.section].arrayOfSteps[indexPath.row-1].stepName
cell.layer.cornerRadius = 10
return cell

Related

Beginner question on passing data between view controllers

I am trying to recreate the Notes app in iOS. I have created an initial View Controller which is just a table view. A user can go to a Detail View Controller to compose a new note with a Title and Body section. When they click Done, I want to manipulate the tableView with note's details.
I am struggling saving the details of what the user entered to use on my initial view controller.
Here's my Notes class which defines the notes data:
class Notes: Codable {
var titleText: String?
var bodyText: String?
}
Here is the Detail View controller where a user can input Note details:
class DetailViewController: UIViewController {
#IBOutlet var noteTitle: UITextField!
#IBOutlet var noteBody: UITextView!
var noteDetails: Notes?
var noteArray = [Notes]()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(updateNote))
noteTitle.borderStyle = .none
}
#objc func updateNote() {
noteDetails?.titleText = noteTitle.text
noteDetails?.bodyText = noteBody.text
noteArray.append(noteDetails!) // This is nil
// not sure if this is the right way to send the details over
// let vc = ViewController()
// vc.noteArray.append(noteDetails!)
if let vc = storyboard?.instantiateViewController(identifier: "Main") {
navigationController?.pushViewController(vc, animated: true)
}
}
}
I also have an array on my initial view controller as well. I think I need this one to store note data to display in the tableView (and maybe don't need the one on my Detail View controller?). The tableView is obviously not completely implemented yet.
class ViewController: UITableViewController {
var noteArray = [Notes]()
override func viewDidLoad() {
super.viewDidLoad()
print(noteArray)
self.navigationItem.setHidesBackButton(true, animated: true)
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(composeNote))
}
#objc func composeNote() {
if let dvc = storyboard?.instantiateViewController(identifier: "Detail") as? DetailViewController {
navigationController?.pushViewController(dvc, animated: true)
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
noteArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
return cell
}
Just using Delegate:
First create delegate protocol with a func to send back note to your viewController
protocol DetailViewControllerDelegate: AnyObject {
func newNoteDidAdded(_ newNote: Note)
}
Next add the delegate variable to DetailViewController, and call func noteDataDidUpdate to send data back to viewController
class DetailViewController: UIViewController {
weak var delegate: DetailViewControllerDelegate?
#objc func updateNote() {
....
delegate?.newNoteDidAdded(newNote)
}
}
finally, set delegate variable to viewController and implement this in ViewController
class ViewController: UIViewController {
....
#objc func composeNote() {
if let dvc = storyboard?.instantiateViewController(identifier: "Detail") as? DetailViewController {
dvc.delegate = self
navigationController?.pushViewController(dvc, animated: true)
}
}
}
extension ViewController: DetailViewControllerDelegate {
func newNoteDidAdded(_ newNote: Note) {
// do some thing with your new note
}
}

PerformSegue from long press on a a custom UiTableViewCell

maybe the question has been asked several times, but can't find it.
I'm a newbie.
I have a UITableView with a custom UITableViewCell.
in the cell there are 3 different labels.
I added the gesture recognizer on the custom cell, so that if long press is done on a label :
- label 1 should show another UiViewController
- label 2 should do a call
- label 3 should create a mail
for labels 2 and 3 i had no problem
but how to perform the segue to open the view controller ?
This is the storyboard
This is the custom tableviewcell
import UIKit
import MessageUI
class OfficeCell: UITableViewCell, MFMailComposeViewControllerDelegate {
#IBOutlet weak var lbOffice: UILabel!
#IBOutlet weak var lbAddress: UILabel!
#IBOutlet weak var lbPhone: UILabel!
#IBOutlet weak var lbMail: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
self.setupLabelTap()
// Configure the view for the selected state
}
func setupLabelTap() {
let lbAddressTap = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressReconizer(_:)))
self.lbAddress.isUserInteractionEnabled = true
self.lbAddress.addGestureRecognizer(lbAddressTap)
let lbAddressTap2 = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressReconizer(_:)))
self.lbMail.isUserInteractionEnabled = true
self.lbMail.addGestureRecognizer(lbAddressTap2)
let lbAddressTap3 = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressReconizer(_:)))
self.lbPhone.isUserInteractionEnabled = true
self.lbPhone.addGestureRecognizer(lbAddressTap3)
}
#objc func longPressReconizer(_ sender: UITapGestureRecognizer) {
print("labelTapped")
let etichetta :UILabel = sender.view as! UILabel
print (etichetta.text!)
switch etichetta.tag {
case 0:
self.performSegue(withIdentifier: "moveToMaps", sender: self)
case 1:
let telNumber = etichetta.text!.replacingOccurrences(of: " ", with: "")
if let phoneCallURL = URL(string: "telprompt://\(telNumber)") {
let application:UIApplication = UIApplication.shared
if (application.canOpenURL(phoneCallURL)) {
application.open(phoneCallURL, options: [:], completionHandler: nil)
}
}
case 2:
if MFMailComposeViewController.canSendMail() {
let mail = MFMailComposeViewController()
mail.mailComposeDelegate = self
mail.setToRecipients([etichetta.text!])
mail.setSubject("Informazioni")
self.window?.rootViewController?.present(mail, animated: true)
} else {
// show failure alert
}
default:
print("default")
}
}
func mailComposeController(_ controller:MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true)
}
}
but Xcode give me this error
Value of type 'OfficeCell' has no member 'performSegue'
on
self.performSegue(withIdentifier: "moveToMaps", sender: self)
how to achieve what i need ?
Thanks in advance
Fabrizio
you need to implement delegate and perform segue from main class because cell class can't perform segue ... in storyBoard attach "moveToMaps" segue from main controller
protocol CellDelegate {
func didTapAddressButton()
}
class OfficeCell: UITableViewCell, MFMailComposeViewControllerDelegate {
var delegate: CellDelegate?
}
In your main View controller class
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "OfficeCell", for: indexPath) as! OfficeCell
cell.delegate = self
return cell
}
extension MainController: CellDelegate {
func didTapAddressButton(){
self.performSegue(withIdentifier: "moveToMaps", sender: self)
}
}

my Callback in CollectionViewCell doesn't work correctly

I have a simple CollectionViewCell with PhoneNumberTextField and a callback that I want to send to my server
class PhoneNumberCollectionViewCell: UICollectionViewCell, NiBLoadable, UITextFieldDelegate {
#IBOutlet weak var phoneLabel: UILabel!
#IBOutlet weak var PhoneNumberTextField: UITextField!
#IBOutlet weak var SecurityLabel: UILabel!
var returnValue: ((_ value: String) -> ())?
override func awakeFromNib() {
super.awakeFromNib()
Decorator.decorate(self)
}
func setPhoneLabelText(text: String) {
phoneLabel.text = text
}
func setSecurityLabel(text: String) {
SecurityLabel.text = text
}
func textFieldDidEndEditing(_ textField: UITextField) {
returnValue?(PhoneNumberTextField.text ?? "") // Use callback to return data
}
}
and I also have my ViewController with my CollectionView and I cant get my phoneNumber from cell to variable in ViewController. Here is come code
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let models = model[indexPath.row]
switch models {
case .phoneNumber:
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PhoneNumberCollectionViewCell.name, for: indexPath) as? PhoneNumberCollectionViewCell {
cell.setSecurityLabel(text: "_ALLYOURDATAISINSECUREDAREA")
cell.setPhoneLabelText(text: "_YOURPHONENUMBER")
cell.PhoneNumberTextField.text = phoneNumber
cell.returnValue = { value in
self.phoneNumber = value
}
return cell
}
and when I print what I got from textField I got nil
#objc func sendPhoneNumber() {
print("PHONE NUMBER IS - \(String(describing: self.phoneNumber))")
}
what's wrong am I doing???
You need to set the delegate for the textField, or the textField delegates wont work.
override func awakeFromNib() {
super.awakeFromNib()
Decorator.decorate(self)
PhoneNumberTextField.delegate = self
}
Side note:
Don't capitalize variables and constants.
Edit to secondary question:
You either want to (through the Xib) make an IBAction outlet that is called on value changed, or:
textField.addTarget(self, action: #selector(textFieldValueChanged), for: .editingChanged)
both options will call the delegate function every time a letter is typed or removed.
Have you registered your collection view cell and declare the delegate and datasource?
let flowLayout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
collectionView(PhoneNumberCollectionViewCell.self, forCellWithReuseIdentifier: PhoneNumberCollectionViewCell.name)
collectionView.delegate = self
collectionView.dataSource = self
return collectionView

Selected table cell information not displaying in detail view using core data, no error

When a table cell is selected, I want it to populate the textfields on my detail view after segue.
Here is one way I found on this site (I've tried other ways I saw here but had errors/ issues translating the code to use with core data and/or custom cells) that doesn't return errors but does not fill out the fields
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
//(print(tableView.dequeueReusableCell(withIdentifier: "PartyCell", for: indexPath)))
// get selected row (party)
let party = parties[indexPath.row] as NSManagedObject
// create custom cell
let cell = tableView.dequeueReusableCell(withIdentifier: "PartyCell",
for: indexPath) as! PartyCell
// Update the custom cell labels with information from record
cell.nameLabel?.text = party.value(forKeyPath: "name") as? String
cell.sizeLabel.text = party.value(forKeyPath: "size") as? String
cell.contactLabel.text = party.value(forKeyPath: "contact") as? String
cell.locationLabel.text = party.value(forKeyPath: "location") as? String
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
// Create a variable that you want to send based on the destination view controller
// Get a reference to the data by using indexPath shown below
let party = parties[indexPath.row]
// Create an instance of DestinationViewController and pass the variable
let destinationVC = DetailViewController()
destinationVC.nameField.text = party.value(forKeyPath: "name") as? String
destinationVC.sizeField.text = party.value(forKeyPath: "size") as? String
destinationVC.contactField.text = party.value(forKeyPath: "contact") as? String
destinationVC.locationField.text = party.value(forKeyPath: "name") as? String
// Let's assume that the segue name is called playerSegue
// This will perform the segue and pre-load the variable for you to use
destinationVC.performSegue(withIdentifier: "mySegue", sender: self)
}
And here is the detail view with the outlet connections established
import Foundation
import UIKit
import CoreData
class DetailViewController: UIViewController
{
//let party = NSEntityDescription.insertNewObjectForEntityForName("Party", inManagedObjectContext: managedObjectContext) as! Party
#IBOutlet weak var nameField: UITextField!
#IBOutlet weak var sizeField: UITextField!
#IBOutlet weak var contactField: UITextField!
#IBOutlet weak var locationField: UITextField!
override func viewWillAppear(_ animated: Bool)
{
}
}
Three fatal mistakes:
DetailViewController() is not the instance in the storyboard.
Even if it was the outlets are not connected yet right after initializing the controller.
You have to perform the segue on the main view controller, not on the DetailViewController.
The solutions:
In didSelectRowAt call self.performSegue and pass the managed object as sender
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
performSegue(withIdentifier: "mySegue", sender: parties[indexPath.row])
}
In DetailViewController declare a NSManagedObject property.
class DetailViewController: UIViewController
{
var party : NSManagedObject!
...
Implement prepare(for segue and assign the NSManagedObject object to the temporary property.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == "mySegue" else { return }
let detailViewController = segue.destination as! DetailViewController
detailViewController.party = sender as! NSManagedObject
}
In viewDidLoad of DetailViewController assign the properties to the outlets.
override func viewDidLoad()
{
super.viewDidLoad()
nameField.text = party.value(forKey: "name") as? String
sizeField.text = party.value(forKey: "size") as? String
contactField.text = party.value(forKey: "contact") as? String
locationField.text = party.value(forKey: "location") as? String
}
You can just add a var in your detailviewcontroller:
class DetailViewController: UIViewController {
var party: Party!
}
using segue to pass the selected party; so in didSelectRowAt
let destinationVC = UIStoryboard(name: "yourStory", bundle: nil).instantiateViewController(withIdentifier: "yourId") as! DetailViewController()
destinationVC.party = selectedParty
destinationVC.performSegue(withIdentifier: "mySegue", sender: self)
then in your viewDidLoad of your DetailViewController setup your outlets:
override func viewDidLoad() {
super.viewDidLoad()
nameField.text = party.something
//and so on!
}

How to get cell index from uiview in uitableviewcell (swift/xcode)

I have two UIViewControllers called upVote and downVote in MyTableViewCell class. I have added the gesture recognizers for tapping these views programmatically but am not sure how to get the parent cell or index of the cell so I know which cell the UIView that was tapped is in. How do I get the cell or index of the tapped cell in the functions handleTapUp and handleTapDown?
Here is my class
class MyTableViewCell: UITableViewCell {
// MARK: Properties
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var upVote: UIImageView!
#IBOutlet weak var downVote: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
let tapUp = UITapGestureRecognizer(target: self, action: "handleTapUp:")
tapUp.delegate = self
upVote.addGestureRecognizer(tapUp)
let tapDown = UITapGestureRecognizer(target: self, action: "handleTapDown:")
tapDown.delegate = self
downVote.addGestureRecognizer(tapDown)
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func handleTapUp(sender: UITapGestureRecognizer? = nil) {
// handling code
// get cell(index)
}
func handleTapDown(sender: UITapGestureRecognizer? = nil) {
// handling code
// get cell()
}
}
I have tried self.superclass to get the uitableviewcell, and when i print the debug description it say uitableviewcell, but whenever I try to do anything else with the object I get an error that self.superclass is type AnyObject. I have tried declaring/casting self.superclass in many different ways and none have worked. This seems like it should be simple so hopefully I am missing something.
Declare a variable in your customCellClass:-
var indexForCell : NSIndexPath!
In your ViewControllers where you are dequeing the cells in the function func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell add this :-
cell.indexForCell = indexPath
Now access the indexForCell in your functions in your class:-
func handleTapUp(sender: UITapGestureRecognizer? = nil) {
let rowValue = indexForCell.row
}
func handleTapDown(sender: UITapGestureRecognizer? = nil) {
// handling code
// get cell()
}