Cell is not selectable embed UItableView inside main View? - ios

I have UITableView embed inside a main UIView. My problem is when I click on a cell to select items, it is not responding to the selection or triggering the segue.
Note: In attributes inspecter
is selection supposed to be set to single selection?
Update Note: the prepareForSegue function is not triggering or print "test".
import UIKit
import SwiftValidator
import CoreData
class EditNpFormViewController: UIViewController,UITextFieldDelegate, UIViewControllerTransitioningDelegate{
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(EditNpFormViewController.dismissKeybored)))
self.npVisitViewTable.dataSource = self
self.npVisitViewTable.delegate = self
loadValidaValidationSettings()
loadUIObjectsSetting()
populateUIobjects()
}
func loadValidaValidationSettings() {
validator.styleTransformers(success:{ (validationRule) -> Void in
// clear error label
validationRule.errorLabel?.hidden = true
validationRule.errorLabel?.text = ""
if let textField = validationRule.field as? UITextField {
textField.layer.borderColor = UIColor.greenColor().CGColor
textField.layer.borderWidth = 0.5
}
}, error:{ (validationError) -> Void in
print("error")
validationError.errorLabel?.hidden = false
validationError.errorLabel?.text = validationError.errorMessage
if let textField = validationError.field as? UITextField {
textField.layer.borderColor = UIColor.redColor().CGColor
textField.layer.borderWidth = 1.0
}
})
validator.registerField(firstNameTextField, errorLabel:firstNameErrorLabel , rules: [RequiredRule(), AlphaRule()])
validator.registerField(lastNameTextField, errorLabel:lastNameErrorLabel , rules: [RequiredRule(), AlphaRule()])
validator.registerField(brnTextFieled, errorLabel:brnErrorLabel, rules: [RequiredRule(), AlphaNumericRule()])
}
func loadUIObjectsSetting(){
self.firstNameTextField.delegate = self
self.lastNameTextField.delegate = self
self.brnTextFieled.delegate = self
self.pickerLhsc.dataSource = self
self.pickerLhsc.delegate = self
self.pickerLhsc.tag = 0
self.ltchTextFieled.inputView = pickerLhsc
self.ltchTextFieled.delegate = self
self.ltchTextFieled.hidden = true
}
func populateUIobjects(){
self.firstNameTextField.text = selectedNpForm!.patientFirstName
self.lastNameTextField.text = selectedNpForm!.patientLastName
self.brnTextFieled.text = selectedNpForm!.brnNumber
self.ltchTextFieled.text = pickerDataLtch.filter { $0.uniqId == selectedNpForm!.ltch }.first?.hospital ?? ""
self.isPatientLtchResidentSwitch.setOn((selectedNpForm!.isPatientLtchResident == 0 ?false : true), animated: true)
self.ltchTextFieled.hidden = !(isPatientLtchResidentSwitch.on ? true : false)
reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: - Methods
func dismissKeybored(){
firstNameTextField.resignFirstResponder()
lastNameTextField.resignFirstResponder()
brnTextFieled.resignFirstResponder()
ltchTextFieled.resignFirstResponder()
}
func textFieledShouldReturn(){
firstNameTextField.resignFirstResponder()
lastNameTextField.resignFirstResponder()
brnTextFieled.resignFirstResponder()
ltchTextFieled.resignFirstResponder()
}
func hideKeyboard(){
self.view.endEditing(true)
}
func validationSuccessful() {
print("Validation Success!")
}
func validationFailed(errors:[(Validatable, ValidationError)]) {
print("Validation FAILED!")
// turn the fields to red
for (field, error) in errors {
if let field = field as? UITextField {
field.layer.borderColor = UIColor.redColor().CGColor
field.layer.borderWidth = 1.0
}
error.errorLabel?.text = error.errorMessage // works if you added labels
error.errorLabel?.hidden = false
}
}
func reloadData(predicate: NSPredicate? = nil) {
if let selectedNpForm = selectedNpForm {
if let formVisits = selectedNpForm.visits?.allObjects {
npVisits = formVisits as! [NpVisit]
}
} else {
let fetchRequest = NSFetchRequest(entityName: "NpVisit")
fetchRequest.predicate = predicate
do {
if let results = try moc.executeFetchRequest(fetchRequest) as? [NpVisit] {
npVisits = results
}
} catch {
fatalError("There was an error fetching the list of devices!")
}
}
npVisitViewTable.reloadData()
}
//MARK: - #IBAction
#IBAction func EditBrn(sender: AnyObject) {
print("Validating...")
//validator.validate(self)
}
#IBAction func saveNpForm(sender: AnyObject) {
if let selectedNpFormId = moc.objectWithID(selectedNpForm!.objectID) as? NpForm{
selectedNpFormId.brnNumber = brnTextFieled.text
selectedNpFormId.patientFirstName = firstNameTextField.text
selectedNpFormId.patientLastName = lastNameTextField.text
selectedNpFormId.isPatientLtchResident = isPatientLtchResidentSwitch.on ? true : false
selectedNpFormId.ltch = hospitalUniqId
}
do {
try moc.save()
} catch let error as NSError {
print("Could not save \(error), \(error)")
}
}
#IBAction func dosePatientResideLtch(sender: AnyObject) {
self.ltchTextFieled.hidden = !(isPatientLtchResidentSwitch.on ? true : false)
}
// MARK: Validate single field
// Don't forget to use UITextFieldDelegate
func textFieldShouldReturn(textField: UITextField) -> Bool {
validator.validateField(textField){ error in
if error == nil {
// Field validation was successful
} else {
// Validation error occurred
}
}
return true
}
//MARK: - Segue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "addNewNoVisit"){
if let distination = segue.destinationViewController as? MapViewController{
distination.clientNpForm._patientFirstName = firstNameTextField.text!
distination.clientNpForm._patientLastName = lastNameTextField.text!
distination.clientNpForm._brnNumber = brnTextFieled.text!
distination.clientNpForm._isPatientLtchResident = isPatientLtchResidentSwitch.on ? true : false
distination.clientNpForm._ltch = hospitalUniqId
distination.editNpFormMode = editNpFormMode
distination.selectedNpForm = selectedNpForm
}
}
if (segue.identifier == "sendNpVisitToVisitDetailSegue"){
print("test segue fired ")
}
}
//MARK: - #IBOutlet
#IBOutlet weak var firstNameTextField: UITextField!
#IBOutlet weak var lastNameTextField: UITextField!
#IBOutlet weak var brnTextFieled: UITextField!
#IBOutlet weak var npVisitViewTable: UITableView!
#IBOutlet weak var firstNameErrorLabel: UILabel!
#IBOutlet weak var lastNameErrorLabel: UILabel!
#IBOutlet weak var brnErrorLabel: UILabel!
#IBOutlet weak var isPatientLtchResidentSwitch: UISwitch!
#IBOutlet weak var ltchTextFieled: UITextField!
#IBOutlet weak var saveNpForm: UIButton!
//MARK: -VARIABLES
let validator = Validator()
var pickerLhsc = UIPickerView()
var editNpFormMode = FormMode.Edit
var selectedNpForm = NpForm?()
var npVisits = [NpVisit]()
var moc = DataController().managedObjectContext
var hospitalUniqId:Int = 0
var pickerDataLtch:[(uniqId:Int,hospital:String)] = [(229,"hospital 1"),
(230,"hospital 2"),
(231,"hospital 3")]
}
extension EditNpFormViewController:UIPickerViewDataSource,UIPickerViewDelegate {
// MARK, - Picker View
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
//return pickerDataLhsc.count
return pickerDataLtch.count
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int){
hospitalUniqId = pickerDataLtch[row].uniqId
ltchTextFieled.text = pickerDataLtch[row].hospital
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int)-> String? {
return pickerDataLtch[row].hospital
}
}
extension EditNpFormViewController: UITableViewDelegate,UITableViewDataSource{
func tableView(npVisitViewTable: UITableView, numberOfRowsInSection section: Int) -> Int{
return npVisits.count
}
func tableView(npVisitViewTable: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell:UITableViewCell = npVisitViewTable.dequeueReusableCellWithIdentifier("visitCell")! as UITableViewCell
cell.textLabel?.text = "Visit #\(indexPath.row + 1)"
return cell
}
func tableView(npVisitViewTable: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
}
func tableView(npVisitViewTable: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("selected row ")
}
}

In this answer I am making the assumption that didSelectRowAtIndexPath is firing.
In your didSelectRowAtIndexPath, you need to fire the segue that you created from the cell to the second view controller:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("selected row", indexPath)
performSegue(with: "segueIdentifier")
}
I'm not sure what version of Swift you are using, so you might need to make some slight changes.

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//self.performSegue(withIdentifier: "sites", sender: self)
self.performSegue(withIdentifier: "CHANGE-TO-YOUT-SEGUE-ID", sender: self)
}
is the "hello" get printed? if so, call your segue from this point. Don't forget to connect your segue (from the top left square) in the story board as manual segue:

If func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) is not called at all, your problem is that the delegate of your UITableView is not set (or not correctly set). The delegate should be your EditNpFormViewController.
You can easily set the delegate of your UITableView instance by simply Control-dragging from the table to the controller that contains it (inside the Storyboard). The other simple way is by setting
tableView.delegate = self
You can just select the table inside the storyboard and drag the delegate from the right panel's circle to your controller:

If your delegate is set right, check the selection attribute of the cell itself:

If your table view is in editing mode, you need to set tableView.allowsSelectionDuringEditing = true

Related

How to pass data between custom table view cells?

I have two custom table views (not table view controller) with custom
cells. I want to select a second cell from MainViewcontroller
and selected
title and price of cell info pass to the second view as
DetailViewController's first and second index labels.
This is my MainViewController.swift
Edit: Here is the answer
import UIKit
class MainViewController: UIViewController, UITableViewDataSource, UITableViewDelegate,
UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var mainTableView: UITableView!
var imageNames = [ImageNames]()
var searchFoods: [String]!
var priceFood: [Double]!
var searching = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.isHidden = false
}
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.isHidden = true
let foodCell = Food(name: ["Hamburger big mac",
"Patates",
"Whopper",
"Steakhouse"], price: [15.0, 20.0, 25.0, 30.0])
searchBar.delegate = self
searchFoods = foodCell.name
priceFood = foodCell.price
imageNames = [
ImageNames(name: "images"),
ImageNames(name: "unnamed"),
ImageNames(name: "unnamed")
]
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return section == 0 ? 1 : searchFoods.count
// return section == 0 ? 1 : foodNames.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return indexPath.section == 0 ? 130 : 65
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return indexPath.section == 0 ? 100 : 65
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "MainFoodTableViewCell", for: indexPath) as! MainFoodTableViewCell
cell.mainFoodCollectionView.delegate = self
cell.mainFoodCollectionView.dataSource = self
cell.mainFoodCollectionView.reloadData()
cell.mainFoodCollectionView.tag = indexPath.row
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellForFood", for: indexPath) as! MainFoodTitleTableViewCell
cell.titleLabel?.text = searchFoods[indexPath.row]
cell.priceLabel?.text = priceFood[indexPath.row].description
return cell
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "cellForFoodSegue" {
if let destinationViewController = segue.destination as? DetailViewController
{
let indexPath = self.mainTableView.indexPathForSelectedRow!
var foodNameArray: [String]
var foodPriceArray: [Double]
foodNameArray = [searchFoods[indexPath.row]]
foodPriceArray = [priceFood[indexPath.row]]
destinationViewController.detailFoodName = foodNameArray
destinationViewController.detailFoodPrice = foodPriceArray
}
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageNames.count
}
//MARK:- collection view cell size
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = UIScreen.main.bounds.width
return CGSize(width: width, height: 130)
}
//MARK:- //collection view cell data
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MainFoodCollectionViewCell", for: indexPath) as! MainFoodCollectionViewCell
let img = imageNames[indexPath.row]
cell.mainFoodImage.image = UIImage(named: img.name)
return cell
}
}
//MARK:- SearchBar data
extension MainViewController : UISearchBarDelegate {
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
self.searchBar.showsCancelButton = true
mainTableView.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
mainTableView.reloadData()
searchBar.showsCancelButton = false
searchBar.text = ""
searchBar.resignFirstResponder()
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchFoods = searchText.isEmpty ? searchFoods : searchFoods.filter { (item: String) -> Bool in
return item.range(of: searchText, options: .caseInsensitive, range: nil, locale: nil) != nil
}
mainTableView.reloadData()
}
}
Edit: Here is my DetailViewController
import UIKit
class DetailViewController: UIViewController {
#IBOutlet weak var foodTitle: UILabel!
#IBOutlet weak var foodSubTitle: UILabel!
#IBOutlet weak var foodPiece: UILabel!
#IBOutlet weak var foodPrice: UILabel!
#IBOutlet weak var drinkPicker: UITextField!
#IBOutlet weak var menuPieceStepper: UIStepper!
var drinkPickerView = UIPickerView()
var selectDrinkType: [String] = []
var detailFoodName : [String] = []
var detailFoodPrice : [Double] = [0.0]
let foods = Food(name: ["Hamburger big mac",
"Patates",
"Whopper",
"Steakhouse"], price: [15.0, 20.0, 25.0, 30.0])
#IBAction func foodPieceStepper(_ sender: Any) {
}
#objc func foodPieceChangeStepper() {
let res = menuPieceStepper.value + foods.price.first!
foodPrice.text = "\(res)"
}
#IBAction func addBasket(_ sender: Any) {
let destinationVC = MyCartViewController()
destinationVC.fromDetailFoodNames = foods.name
destinationVC.fromDetailFoodPrices = foods.price
dismiss(animated: true)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "addToCartSegue") {
if let addToCartVC = segue.destination as? MyCartViewController {
addToCartVC.fromDetailFoodNames = [foodTitle.text]
addToCartVC.fromDetailFoodPrices = foods.price
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
menuPieceStepper.value = 0.0
menuPieceStepper.minimumValue = 0.0
menuPieceStepper.maximumValue = 30.0
menuPieceStepper.stepValue = foods.price.first!
menuPieceStepper.addTarget(self, action: #selector(foodPieceChangeStepper), for: .valueChanged)
drinkPickerView.delegate = self
drinkPicker.inputView = drinkPickerView
selectDrinkType = ["Ayran", "Kola", "Su", "Fanta", "Şalgam", "Sprite"]
foodTitle.text = detailFoodName.description
foodPrice.text = detailFoodPrice.description
self.navigationController?.navigationItem.title = "Sipariş Detayı"
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard (_:)))
self.view.addGestureRecognizer(tapGesture)
}
#objc func dismissKeyboard (_ sender: UITapGestureRecognizer) {
drinkPicker.resignFirstResponder()
}
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.navigationBar.isHidden = false
}
override func viewWillDisappear(_ animated: Bool) {
self.navigationController?.navigationBar.isHidden = true
}
}
extension DetailViewController: UIPickerViewDelegate, UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return selectDrinkType.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return selectDrinkType[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let selectedDrink = selectDrinkType[row]
drinkPicker.text = selectedDrink
}
}
Aren't you looking for prepare(for segue: sender:) method? Usually you need to bind a cell prototype to a detail view with a segue, and when the cell is selected the segue is followed just after that method is called to prepare data injection to the detail view.

How to design the array structure for Multiple cells in UITableview?

This is my first ViewController ,when I will click on plus(+) button, it will move to the SecondViewController
This is my SecondViewController, after giving the required field when I will click on save button it should add one more row in my tableview respective cell(executive,seniormanagement,staff). But it's not adding the row in respective cells.
This my code what i have tired
FirstViewController
class TableViewHeader: UIViewController ,UITableViewDelegate,UITableViewDataSource,EmployeeProtocol{
#IBOutlet weak var tableviewHeader: UITableView!
var employee = [ EmployeeStatus(status:"Executive",Name:["Smith","Dev","Aryan"], Date:["Nov 14 1889","Dec 01 1980","Sep 18 1997"]),
EmployeeStatus(status:"SeniorManagement",Name:["Sourav","Smarak"], Date:[" April 10 1879"," Dec 11 1990"]),
EmployeeStatus(status:"Staff",Name:["Smith","Dev","Aryan","Suman"], Date:["Feb 14 1234"," Jan 01 1480","Sep 23 1994","July 10 1991"])]
#IBAction func plusButtonTapped(_ sender: UIButton) {
performSegue(withIdentifier: "showSegue", sender:self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showSegue" {
let createSegue = segue.destination as! CreateEmployeeViewController
createSegue.myEmpProtocol = self
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return employee.count
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return employee[section].Name.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let Label = UILabel()
Label.text = employee[section].status
Label.backgroundColor = UIColor.gray
return Label
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableviewHeader.dequeueReusableCell(withIdentifier: "TableHeaderCell", for: indexPath) as! TableViewHeadercell
cell.NameLabel.text = employee[indexPath.section].Name[indexPath.row]
cell.DateLabel.text = employee[indexPath.section].Date[indexPath.row]
return cell
}
func addData(status:String, Name:[String], Date:[String]) {
employee.append(EmployeeStatus(status:status, Name:Name, Date: Date))
tableviewHeader.reloadData()
}
}
SeconViewController
protocol EmployeeProtocol {
func addData(status:String, Name:[String], Date:[String])
}
class CreateEmployeeViewController: UIViewController {
var myEmpProtocol:EmployeeProtocol?
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var birthdayTextField: UITextField!
#IBOutlet weak var StatusSegment: UISegmentedControl!
var name = [String]()
var DOB = [String]()
var status:String = ""
#IBAction func saveButtonTapped(_ sender: UIButton) {
self.name = [nameTextField.text!]
self.DOB = [birthdayTextField.text!]
if StatusSegment.selectedSegmentIndex == 0 {
self.status = "Executive"
} else if StatusSegment.selectedSegmentIndex == 1{
self.status = "SeniorManagement"
} else if StatusSegment.selectedSegmentIndex == 2 {
self.status = "Staff"
}
myEmpProtocol?.addData(status:status, Name:name, Date:DOB)
}
#IBAction func CancelButtonTapped(_ sender: UIButton) {
dismiss(animated: true, completion: nil)
}
}
MyClass
import Foundation
struct EmployeeStatus {
var status:String
var Name:[String]
var Date:[String]
init(status:String, Name:[String], Date:[String]){
self.status = status
self.Name = Name
self.Date = Date
}
Your current code is adding the new EmployeeStatus to employee array, not the item's EmployeeStatus.Name array and EmployeeStatus.Date array.
Please try this code.
func addData(status:String, Name:[String], Date:[String]) {
var arr = [EmployeeStatus]()
employee.forEach { ele in
if ele.status == status {
var e = ele
e.Name.append(Name[0])
e.Date.append(Date[0])
arr.append(e)
} else {
arr.append(ele)
}
}
employee = arr
tableviewHeader.reloadData()
}
Your data looks interesting. It might work better with
enum Level: Int {
case executive = 0, seniorManagement, staff
var stringValue: { return (String:description: self).capitalizingFirstLetter }
}
struct Employee {
let name: String
let date: String
}
var employees: [Level: [Employee]] = [
.executive: [Employee(name: "Smith", date: "Nov 14 1889"],
.seniorManagement: [],
.staff: []]
Then you can add employees with:
guard let level = Level(rawValue: status) else {
throw Errors.levelDoesNotExist(status)
}
employees[.level] = newEmployee
Reloading the tableView works as you do already. Just the data is in a format where its weird to add to.
And best dismiss the second viewController once its finished after the addData delegate call so its going back to the first to show the updated data.
You have to set your view controller as a table view delegate and datasource.
Add to the end of the viewDidLoad at TableViewHeader class:
override func viewDidLoad() {
super.viewDidLoad()
tableviewHeader.delegate = self
tableviewHeader.dataSource = self
}
In this way, you can pass value from one view controller to another tab:
First of all open main.storyboard
Select the First view controller and drag and drop UItextField and UIButton on the field.

How to segue back to filled-in version of TableViewController (Swift)?

I am building a room-booking app for iOS in Xcode 9.3.
Here is the basic layout:
First TableViewController(TVC1): starts empty. Pressing '+' pops up the
Second TableViewController(TVC2) with many fields to fill in.
Once the 'Done' button on TVC2 is pressed I get back to TVC1 which now has a cell (Subtitle style) containing the details inserted.
I would now like to tap on said cell and get back to TVC2 to either check or modify the data.
I have created the segue but upon tapping I get the same version of TVC2 that I get when pressing '+', not the filled in one.
What am I doing wrong?
This is the code relative to TVC1 that I need to edit:
import UIKit
class RegistrationTableViewController: UITableViewController {
var registrations: [Registration] = []
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return registrations.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RegistrationCell", for: indexPath)
let registration = registrations[indexPath.row]
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
cell.textLabel?.text = registration.firstName + " " + registration.lastName
cell.detailTextLabel?.text = dateFormatter.string(from: registration.checkInDate) + " - " + registration.roomType.name
return cell
}
#IBAction func unwindFromAddRegistration(unwindSegue: UIStoryboardSegue) {
guard let addRegistrationTableViewController = unwindSegue.source as? AddRegistrationTableViewController,
let registration = addRegistrationTableViewController.registration else { return }
registrations.append(registration)
tableView.reloadData()
}
// MARK: Challenge.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ViewReservationDetails" {
}
}
And here is the code for (TVC2) so that you have more or less all the app available.
import UIKit
class AddRegistrationTableViewController: UITableViewController, SelectRoomTypeTableViewControllerDelegate {
// MARK: Properties
let checkInDatePickerCellIndexPath = IndexPath(row: 1, section: 1)
let checkOutDatePickerCellIndexPath = IndexPath(row: 3, section: 1)
var isCheckInDatePickerShown: Bool = false {
didSet {
checkInDatePicker.isHidden = !isCheckInDatePickerShown
}
}
var isCheckOutDatePickerShown: Bool = false {
didSet {
checkOutDatePicker.isHidden = !isCheckOutDatePickerShown
}
}
var roomType: RoomType?
var registration: Registration? {
guard let roomType = roomType else { return nil }
let firstName = firstNameTextField.text ?? ""
let lastName = lastNameTextField.text ?? ""
let email = emailTextField.text ?? ""
let checkInDate = checkInDatePicker.date
let checkOutDate = checkOutDatePicker.date
let numberOfAdults = Int(numberOfAdultsStepper.value)
let numberOfChildren = Int(numberOfChildrenStepper.value)
let hasWifi = wifiSwitch.isOn
return Registration(firstName: firstName, lastName: lastName, emailAddress: email, checkInDate: checkInDate, checkOutDate: checkOutDate, numberOfAdults: numberOfAdults, numberOfChildren: numberOfChildren, roomType: roomType, wifi: hasWifi)
}
var selectedItem: Registration?
// MARK: Outlets
#IBOutlet weak var firstNameTextField: UITextField!
#IBOutlet weak var lastNameTextField: UITextField!
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var checkInDateLabel: UILabel!
#IBOutlet weak var checkInDatePicker: UIDatePicker!
#IBOutlet weak var checkOutDateLabel: UILabel!
#IBOutlet weak var checkOutDatePicker: UIDatePicker!
#IBOutlet weak var numberOfAdultsLabel: UILabel!
#IBOutlet weak var numberOfAdultsStepper: UIStepper!
#IBOutlet weak var numberOfChildrenLabel: UILabel!
#IBOutlet weak var numberOfChildrenStepper: UIStepper!
#IBOutlet weak var roomTypeLabel: UILabel!
#IBOutlet weak var wifiSwitch: UISwitch!
// MARK: Actions
#IBAction func datePickerValueChanged(_ sender: UIDatePicker) {
updateDateViews()
}
#IBAction func stepperValueChanged(_ sender: UIStepper) {
updateNumberOfGuests()
}
#IBAction func wifiSwitchChanged(_ sender: UISwitch) {
// implemented later
}
#IBAction func cancelButtonTapped(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
// MARK: Methods
func updateDateViews() {
checkOutDatePicker.minimumDate = checkInDatePicker.date.addingTimeInterval(86400)
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
checkInDateLabel.text = dateFormatter.string(from: checkInDatePicker.date)
checkOutDateLabel.text = dateFormatter.string(from: checkOutDatePicker.date)
}
func updateNumberOfGuests() {
numberOfAdultsLabel.text = "\(Int(numberOfAdultsStepper.value))"
numberOfChildrenLabel.text = "\(Int(numberOfChildrenStepper.value))"
}
func updateRoomType() {
if let roomType = roomType {
roomTypeLabel.text = roomType.name
} else {
roomTypeLabel.text = "Not Set"
}
}
func didSelect(roomType: RoomType) {
self.roomType = roomType
updateRoomType()
}
override func viewDidLoad() {
super.viewDidLoad()
let midnightToday = Calendar.current.startOfDay(for: Date())
checkInDatePicker.minimumDate = midnightToday
checkInDatePicker.date = midnightToday
updateDateViews()
updateNumberOfGuests()
updateRoomType()
}
// MARK: TableView Data
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
switch (indexPath.section, indexPath.row) {
case (checkInDatePickerCellIndexPath.section, checkInDatePickerCellIndexPath.row):
if isCheckInDatePickerShown {
return 216.0
} else {
return 0.0
}
case (checkOutDatePickerCellIndexPath.section, checkOutDatePickerCellIndexPath.row):
if isCheckOutDatePickerShown {
return 216.0
} else {
return 0.0
}
default:
return 44.0
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
switch (indexPath.section, indexPath.row) {
case (checkInDatePickerCellIndexPath.section, checkInDatePickerCellIndexPath.row - 1):
if isCheckInDatePickerShown {
isCheckInDatePickerShown = false
} else if isCheckOutDatePickerShown {
isCheckOutDatePickerShown = false
isCheckInDatePickerShown = true
} else {
isCheckInDatePickerShown = true
}
tableView.beginUpdates()
tableView.endUpdates()
case (checkOutDatePickerCellIndexPath.section, checkOutDatePickerCellIndexPath.row - 1):
if isCheckOutDatePickerShown {
isCheckOutDatePickerShown = false
} else if isCheckInDatePickerShown {
isCheckInDatePickerShown = false
isCheckOutDatePickerShown = true
} else {
isCheckOutDatePickerShown = true
}
tableView.beginUpdates()
tableView.endUpdates()
default:
break
}
}
// MARK: Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SelectRoomType" {
let destinationViewController = segue.destination as? SelectRoomTypeTableViewController
destinationViewController?.delegate = self
destinationViewController?.roomType = roomType
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
The challenge to this tutorial says to start from here and:
"Update the RegistrationTableViewController(TVC1) with a segue that allows the user to select and view the details of a registration in the AddRegistrationTableViewController(TVC2).
Hope this helps to provide the right solution.
Create a property let say selectedItem in TVC2 screen.
var selectedItem: Registration?
Modify prepare for cell ...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ViewReservationDetails" {
let sourceCell = sender as! UITableViewCell
if let indexPath = tableView.indexPath(for: sourceCell) {
if let dest = segue.destination as? <#Destination view controller#> {
dest.selectedItem = registrations[indexPath.row]
}
}
}
}
And finally in your destination view controller (having TVC2) you need to check selectedItem and assign value in viewDidLoad: or wherever.
override func viewDidLoad() {
super.viewDidLoad()
if let item = selectedItem {
//here set value as you want to views
}
}
Use this code in tableView(_:didSelectRowAt:):
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
performSegue(withIdentifier: "ViewReservationDetails", sender: indexPath)
}
Then, in TVC1’s prepare(for:sender:) you pass the selected registrations element plus a completion handler to TVC2, like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ViewReservationDetails" {
if let destination = segue.destination as? AddRegistrationTableViewController, let indexPath = sender as? IndexPath {
destination.registration = registrations[indexPath.row]
destination.completionHandler = { (registration) in
self.registrations.append(registration)
self.tableView.reloadData()
}
}
}
}
In TVC2, you declare a property completionHandler like this:
var completionHandler: ((Registration) -> Void)?
and you call this handler in viewWillDisappear:
completionHandler?(newRegistration)
with newRegistration being the newly created element to be added to the Registration array. This way, you won’t need an unwind segue.

iOS development Swift Custom Cells

I'm developing an app in Swift and I have problem with Custom Cells. I have one Custom Cell and when you click on Add button it creates another Custom Cell. Cell has 3 textfields, and 2 buttons. Those textfields are name, price, and amount of the ingredient of meal that I am creating. When I use only one Custom Cell or add one more to make it two, the data is stored properly. But when I add 2 Custom Cell (3 Custom Cells in total) I have problem of wrong price, only last two ingredients are calculated in price. When I add 3 Custom Cells (4 Custom Cells in total) it only recreates first cell with populated data like in first cell.
On finish button tap, I get an fatal error: Found nil while unwrapping Optional value.
View Controller
import UIKit
import CoreData
class AddMealViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {
#IBOutlet weak var mealNameTF: UITextField!
#IBOutlet weak var addMealsCell: UITableViewCell!
#IBOutlet weak var finishButton: UIButton!
#IBOutlet weak var resetButton: UIButton!
#IBOutlet weak var addButton: UIButton!
#IBOutlet weak var addMealTableView: UITableView!
#IBOutlet weak var productPrice: UILabel!
let currency = "$" // this should be determined from settings
var priceTotal = "0"
override func viewDidLoad() {
super.viewDidLoad()
addMealTableView.delegate = self
addMealTableView.dataSource = self
borderToTextfield(textField: mealNameTF)
mealNameTF.delegate = self
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.view.endEditing(true)
return true;
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return counter
}
var counter = 1
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return counter
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:
"addMealsCell", for: indexPath) as! AddMealsTableViewCell
borderToTextfield(textField: (cell.amountTF)!)
borderToTextfield(textField: (cell.ingredientNameTF)!)
//borderToTextfield(textField: cell.normativeTF)
borderToTextfield(textField: (cell.priceTF)!)
return cell
}
#IBAction func addButton(_ sender: UIButton) {
counter += 1
addMealTableView.register(AddMealsTableViewCell.self, forCellReuseIdentifier: "addMealsCell")
addMealTableView.reloadData()
}
#IBAction func resetButton(_ sender: UIButton) {
mealNameTF.text = ""
for c in 0..<counter{
let indexPath = IndexPath(row: c, section:0)
let cell = addMealTableView.cellForRow(at: indexPath) as! AddMealsTableViewCell
cell.amountTF.text = ""
cell.ingredientNameTF.text = ""
// cell.normativeTF.text = ""
cell.priceTF.text = ""
}
productPrice.text = "\(currency)0.00"
priceTotal = "0"
counter = 1
}
#IBAction func finishButton(_ sender: UIButton) {
for c in (0..<counter){
if let cell = addMealTableView.cellForRow(at: IndexPath(row: c, section: 0)) as? AddMealsTableViewCell {
cell.amountTF.delegate = self
cell.ingredientNameTF.delegate = self
// cell.normativeTF.delegate = self
cell.priceTF.delegate = self
guard cell.priceTF.text?.isEmpty == false && cell.amountTF.text?.isEmpty == false && mealNameTF.text?.isEmpty == false && cell.ingredientNameTF.text?.isEmpty == false
else {
return
}
if cell.priceTF.text?.isEmpty == false{
// if (true) {
let tfp = Double((cell.priceTF.text!))!*Double((cell.amountTF.text!))!
var ttp = Double(priceTotal)
ttp! += tfp
priceTotal = String(ttp!)
// }
}}
}
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let newMeal = NSEntityDescription.insertNewObject(forEntityName: "Meal", into: context)
let mealName = mealNameTF.text
newMeal.setValue(mealName, forKey: "name")
newMeal.setValue(priceTotal, forKey: "price")
do {
try context.save()
print("Spremljeno")
} catch {
print("Neki error")
}
productPrice.text = currency + priceTotal
}
#IBAction func addNewIngredientButton(_ sender: UIButton) {
}
func borderToTextfield(textField: UITextField){
let border = CALayer()
let width = CGFloat(2.0)
border.borderColor = UIColor.white.cgColor
border.frame = CGRect(x: 0, y: textField.frame.size.height - width, width: textField.frame.size.width, height: textField.frame.size.height)
border.borderWidth = width
textField.layer.addSublayer(border)
textField.layer.masksToBounds = true
textField.tintColor = UIColor.white
textField.textColor = UIColor.white
textField.textAlignment = .center
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.view.endEditing(true)
return true;
}
}
Cell
class AddMealsTableViewCell: UITableViewCell, UITextFieldDelegate{
#IBOutlet weak var DropMenuButton: DropMenuButton!
#IBOutlet weak var addNewIngredient: UIButton!
#IBOutlet weak var ingredientNameTF: UITextField!
// #IBOutlet weak var normativeTF: UITextField!
#IBOutlet weak var amountTF: UITextField!
#IBOutlet weak var priceTF: UITextField!
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.endEditing(true)
return true;
}
override func prepareForReuse() {
self.amountTF.text = ""
self.priceTF.text = ""
self.ingredientNameTF.text = ""
}
#IBAction func DropMenuButton(_ sender: DropMenuButton) {
DropMenuButton.initMenu(["kg", "litre", "1/pcs"], actions: [({ () -> (Void) in
print("kg")
sender.titleLabel?.text = "kg"
}), ({ () -> (Void) in
print("litre")
sender.titleLabel?.text = "litre"
}), ({ () -> (Void) in
print("1/pcs")
sender.titleLabel?.text = "1/pcs"
})])
}
#IBAction func addNewIngredient(_ sender: UIButton) {
let name = ingredientNameTF.text
let amount = amountTF.text
let price = priceTF.text
// let normative = normativeTF.text
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let newIngredient = NSEntityDescription.insertNewObject(forEntityName: "Ingredient", into: context)
newIngredient.setValue(name, forKey: "name")
// newIngredient.setValue(normative, forKey: "normative")
newIngredient.setValue(amount, forKey: "amount")
newIngredient.setValue(price, forKey: "price")
do {
try context.save()
print("Spremljeno")
} catch {
print("Neki error")
}
}
}
Your code is very difficult to read, but I suspect the problem may be here:
func numberOfSections(in tableView: UITableView) -> Int {
return counter
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return counter
}
You're returning the same number of rows for both the sections and rows for sections. So if you have 1 ingredient you are saying there is 1 section with 1 row. But if you have 2 ingredients you are saying there are 2 sections, each with 2 cells (4 cells total).
There are many other things to fix with your code, here are a few:
The biggest thing is that you are making this very difficult with the counter variable you have. If instead you have an array of ingredients
var ingredients = [Ingredient]()
You can use that to setup everything for the count of your table. Something like this:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ingredients.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:
"addMealsCell", for: indexPath) as! AddMealsTableViewCell
let ingredient = ingredients[indexPath.row]
cell.ingredientNameTF.text = ingredient.name
cell.normativeTF.text = ingredient.normative
cell.amountTF.text = ingredient.amount
cell.priceTF.text = ingredient.price
return cell
}
All of these can be set in interface builder (you dont need lines of code in your view did load for them):
addMealTableView.delegate = self
addMealTableView.dataSource = self
mealNameTF.delegate = self
This line should be in your viewDidLoad function, you only need to register the class once, you're doing it everytime add is pressed.
addMealTableView.register(AddMealsTableViewCell.self, forCellReuseIdentifier: "addMealsCell")
Your action names should be actions
#IBAction func addButtonPressed(_ sender: UIButton)
#IBAction func resetButtonPressed(_ sender: UIButton)
#IBAction func finishButtonPressed(_ sender: UIButton)
Thanks. Now when that ViewController loads I have no cells. My array is now empty so I have to implement some code to addButton which creates another cell and reloads tableView. How can I do that?
You just need to add a new ingredient object to the ingredients array and reload the data of the table view.
#IBAction func addButtonPressed(_ sender: UIButton) {
let newIngredient = Ingredient()
ingredients.append(newIngredient)
tableView.reloadData()
}

Showing optional value as 'nil' while changing the viewController from UITableView

I'm trying to display details from a table row onto another viewController, there are three entities which I want to display on the second VC. Two UILabel and one UIImageView. While in the first VC I'm able to view, when in the second VC, it says 'Optional("")', And don't know how to unwrap it.
import UIKit
class ViewController: UIViewController, UITextFieldDelegate, UITableViewDelegate,UITableViewDataSource, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet var nameForUser: UITextField!
#IBOutlet var loginButton: UIButton!
#IBOutlet var tableView: UITableView!
let allEvents = Events.allEvents
var nextScreenRow: Events!
override func viewDidLoad() {
super.viewDidLoad()
//nameForUser.text! = "Please Enter Name Here"
// Do any additional setup after loading the view, typically from a nib.
//let userName = nameForUser.text
}
func textFieldDidBeginEditing(textField: UITextField) {
textField.text = ""
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.allEvents.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("eventsCell")!
let event = self.allEvents[indexPath.row]
//print(" row \(indexPath.row)")
cell.textLabel?.text = event.eventName
cell.imageView?.image = UIImage(named: event.imageName)
cell.detailTextLabel?.text = event.entryType
//cell.textLabel?.text = allEvents[indexPath.row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
nextScreenRow = allEvents[indexPath.row]
let view : DetailedEventViewController = self.storyboard?.instantiateViewControllerWithIdentifier("trytry") as! DetailedEventViewController
self.navigationController?.pushViewController(view, animated: true)
print("Selected section \(indexPath.section), row \(indexPath.row)")
print(nextScreenRow.eventName)
view.eventDetails = nextScreenRow.eventName
print(view.eventDetails)
view.typeOfEvent = nextScreenRow.entryType
view.myImage = UIImage(named: nextScreenRow.imageName)
//even the 'print' is used, it is displaying here, but not in the next VC
}
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.allEvents.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let sec = collectionView.dequeueReusableCellWithReuseIdentifier("eventsSec", forIndexPath: indexPath) as! GridCollectionViewCell
let event = self.allEvents[indexPath.row]
sec.imageView.image = UIImage(named: event.imageName)
sec.caption.text = event.entryType
return sec
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("tryToConnect2", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "successfulLogin"){
segue.destinationViewController as! TabController
//let userName = nameForUser.text
//controller.userName = userName
}
}
#IBAction func loginButtonWhenPressed(sender: UIButton) {
let userName = nameForUser.text
if userName == "" {
let nextController = UIAlertController()
nextController.title = "Error!"
nextController.message = "Please enter a name"
let okAction = UIAlertAction(title: "okay", style: UIAlertActionStyle.Default) {
action in self.dismissViewControllerAnimated(true, completion: nil)
}
nextController.addAction(okAction)
self.presentViewController(nextController, animated: true, completion: nil)
}
}
}
And here is my second VC:
import UIKit
class DetailedEventViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var deatiledEvent: UILabel!
#IBOutlet weak var eventType: UILabel!
var eventDetails = ""
var typeOfEvent = ""
var myImage: UIImage?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func viewTheElemnts(sender: AnyObject) {
deatiledEvent.text = eventDetails
print(deatiledEvent.text)
eventType.text = typeOfEvent
imageView.image = myImage
}
}
Help would be appreciated greatly. Thank you.
You are creating an instance of DetailedEventViewController in the didSelectRowAtIndexPath and setting the value there. But actually you are moving to DetailedEventViewController using segue. So you should add those values in the prepareForSegue: method.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if (segue.identifier == "trytry")
{
let selectedIndex = self.tableView.indexPathForCell(sender as! UITableViewCell)
nextScreenRow = allEvents[selectedIndex.row]
let view = segue.destinationViewController as! DetailedEventViewController
view.eventDetails = nextScreenRow.eventName
view.typeOfEvent = nextScreenRow.entryType
view.myImage = UIImage(named: nextScreenRow.imageName)
}
}
Unwrap your eventName like this :
view.eventDetails = nextScreenRow.eventName! as String
print(view.eventDetails) view.typeOfEvent = nextScreenRow.entryType
view.myImage = UIImage(named: nextScreenRow.imageName)
self.navigationController?.pushViewController(view, animated: true

Resources