PickerView in TableViewCell works but not Update - ios

i made a simple apps for practice. I'm a beginner in programming.
First of all I will explained in detail what I did and what I want to do.
Create a File a Swift file for data :
class done {
var meal: [String] = ["tomato","banana","potato","peanuts"]
}
ViewController Setup I have a Textfield and a Button to go to the next page this will add the data of the textfield that the person enter and will add to my meal data list:
import UIKit
class ViewController: UIViewController {
var data = done()
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
print("Data now : \(data.meal)")
}
#IBAction func buttonaction(_ sender: UIButton) {
var newitem = textField.text ?? ""
if textField.text == "" {
newitem = "empty"
}
data.meal.append(newitem)
performSegue(withIdentifier: "toView", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toView" {
let successVCG = segue.destination as! TableViewController
successVCG.data = self.data
}
}
}
I have a second ViewController that will display in a TableView with a TableViewcell and Inside I have a Pickerview in each row..
import UIKit
class TableViewController: UIViewController{
#IBOutlet weak var table: UITableView!
var data = done()
override func viewDidLoad() {
super.viewDidLoad()
print("Transfert Data OK : \(data.meal)")
table.delegate = self
table.dataSource = self
}
#IBAction func `return`(_ sender: Any) {
print(data.meal)
}
#IBAction func update(_ sender: Any) {
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toViewa" {
let successVCG = segue.destination as! ViewController
successVCG.data = self.data
}
}
}
extension TableViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 300
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "cellule") as? TableViewCell {
return cell
}
let cell2 = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
return cell2
}
}
And it's the configuration of the TableViewCell
import UIKit
class TableViewCell: UITableViewCell {
#IBOutlet weak var picker: UIPickerView!
var data = done()
override func awakeFromNib() {
super.awakeFromNib()
picker.delegate = self
picker.dataSource = self
}
}
extension TableViewCell: UIPickerViewDataSource, UIPickerViewDelegate {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return data.meal.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return data.meal[row]
}
}
The Picker view is display the data.meal correctly but never Updater while I add new stuff in the variable.
In my print. I see the new data update In the ViewController and the TableViewController but I try everything I could with my learning. Can't update the PickerView. I can't send the new value in the TableViewCell...mPlease help. Thank you very Much. Love to code!

This is because you don't implement the pickerView(_:didSelectRow:inComponent:) picker view's delegate method in your table view cell. So even if the user selects a new row from your picker view, the changes simply won't get reflected in your data variable. You could implement this method and change your data model accordingly from within this method.
An other thing I noticed is that you're not passing your data from the parent view to your table view cell, so the data variable in your table view cell never gets the latest data. You could simply pass the data to your cell in the cellForRow(at:) method of your table view.
Also I would recommend you to name your data model something different than done. This name is kind of confusing (because it would usually refer to an action), and the capitalization is misleading because for example when in your code you write var done = done() it feels like you're calling a function or a closure, while, from my understanding, you're actually initializing your data model.
This is because in Swift you would name by convention all your methods, functions and variables using lower camel case (for example let myVariable: Double = 0), and types such as structs, enums and classes in upper camel case (for example myData = MyDataModel()).

Related

Add items to another view controller table view on button click

I have a view controller called ListViewController and another called AddFoodViewController. In ListViewController users are able to add their own ingredients to the grocery list which is presented in a table view. When they go to the AddFoodViewController, users should be able to click a button which says "add to list" which will add the array of ingrediets (that are already presented in a table view) into the grocery list. I am new to this, so I was wondering if anyone can help? I have successfully been able to get the ListViewController to work, however I am not sure how to add the array of ingredients from AddFoodViewController into the previous ListViewController.
class AddFoodViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
#IBOutlet weak var FoodTableView: UITableView!
#IBOutlet weak var sendFoodBtn: UIButton!
//array of food
let array = ["1 Salad", "3oz Chicken", "2 Tomatoes", "2 Cucumbers"]
let category = ""
override func viewDidLoad() {
super.viewDidLoad()
}
//display array in table view
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return(array.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let foodCell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "foodCell")
foodCell.textLabel?.text = array[indexPath.row]
foodCell.backgroundColor = .clear
foodCell.textLabel?.textColor = .darkGray
foodCell.textLabel?.font = UIFont(name: (foodCell.textLabel?.font.fontName)!, size:17)
return foodCell
}
//button that is supposed to add all ingredients to the ListViewController
#IBAction func addOnClick(_ sender: Any) {
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "toList"){
let vc = (segue.destination as! ListViewController)
vc.category = array
}
}
}
Passing data to previous view controller can be implemented by delegation pattern, first at all, declare a protocol in your AddFoodViewController, and define a delegate property in view controller.
// AddFoodViewController.swift
protocol AddFoodViewControllerDelegate {
func addIngredient(array: [String])
}
class AddFoodViewController: UIViewController {
...
var delegate: AddFoodViewControllerDelegate?
// MARK: add function
func actionAdd() {
delegate?addIngredient(array)
}
...
}
Back to your ListViewController,find the segue destination view controller which is your AddFoodViewController (remember to assign its class name in the storyboard), and assign delegate to self.
// ListViewController.swift
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
...
let vc = segue.destination as? AddFoodViewController
vc?.delegate = self
...
}
// in the same file, implement delegate method here
extension ListViewController: AddFoodViewControllerDelegate {
func addIngredient(array: [String]) {
items += array
// do table reload or something
}
}

Call function in secondFile.swift/viewController from firstFile.swift/viewController?

I'm trying to call a function() made in my first ViewController from another function() made in the second ViewController.
It's a function to update the title of a button in the firstViewController.
I have searched but I can't find a way.
First ViewController // ViewController.swift
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
weightLabel.delegate = self
}
#IBAction func excerciseChooserButton(_ sender: UIButton) {
}
var weight = 0 {
didSet {
weightLabel.text = "\(weight)"
}
}
// User input WEIGHT
#IBOutlet weak var weightLabel: UITextField!
func textField(_ weightLabel: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let isNumber = CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: string))
let withDecimal = (
string == NumberFormatter().decimalSeparator &&
weightLabel.text?.contains(string) == false
)
return isNumber || withDecimal
}
#IBAction func plusWeight(_ sender: UIButton) {
weight += 5
}
#IBAction func minusWeight(_ sender: UIButton) {
weight -= 5
}
// User input REPS
#IBOutlet weak var repLabel: UILabel!
#IBAction func repSlider(_ sender: UISlider) {
let currentRepValue = Int(sender.value)
repLabel.text = "\(currentRepValue)"
let cm = Calculator(weight: weightLabel.text!, reps: repLabel.text!)
let result = cm.calcRM()
repMax.text = "1RM: \(result)kg"
}
#IBOutlet weak var repMax: UILabel!
#IBOutlet weak var excerciseLabel: UIButton!
func changeText() {
excerciseLabel.setTitle(Excercises.excChosen, for: .normal)
print(excerciseLabel)
}
#IBAction func unwindToViewController(segue:UIStoryboardSegue) {
}
}
// // // //
Second ViewController // ExcerciseChooserViewController.swift
import UIKit
struct Excercises {
static var excChosen:String? = ""
}
class ExcerciseChooserViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
// Data model: These strings will be the data for the table view cells
let excercises: [String] = ["Bench Press", "Squat", "Push Press", "Deadlift"]
// cell reuse id (cells that scroll out of view can be reused)
let cellReuseIdentifier = "cell"
// don't forget to hook this up from the storyboard
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Register the table view cell class and its reuse id
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
// (optional) include this line if you want to remove the extra empty cell divider lines
// self.tableView.tableFooterView = UIView()
// This view controller itself will provide the delegate methods and row data for the table view.
tableView.delegate = self
tableView.dataSource = self
}
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.excercises.count
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// create a new cell if needed or reuse an old one
let cell:UITableViewCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as UITableViewCell!
// set the text from the data model
cell.textLabel?.text = self.excercises[indexPath.row]
return cell
}
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let excerciseChosen = "\(excercises[indexPath.row])"
print("You tapped cell number \(indexPath.row).")
print(excerciseChosen)
goBackToOneButtonTapped((Any).self)
Excercises.excChosen = excerciseChosen
print(Excercises.excChosen!)
// call function to update text
ViewController.changeText()
}
#IBAction func goBackToOneButtonTapped(_ sender: Any) {
performSegue(withIdentifier: "unwindToViewController", sender: self)
}
}
Call it from unwindToViewController instead, no need to call it while the first view controller is not visible
There are many ways to do this, but I'll describe a simple one here.
Because you're going back to 'ViewController' via a segue, a good option for you is to override prepare(for:sender:). This will give you a reference to the destination view controller of that segue, which will then allow you to call functions or set properties in that view controller. You can read more about this method here.
Here are some basic steps:
In ViewController, update your changeText() method to accept a string parameter: changeText(_ text: String?).
Add a property to ExcerciseChooserViewController to hold the text you want to use: private var chosenExercise: String?
In your tableView:DidSelectRowAtIndexPath: method, set your new chosenExercise property to the string you want to pass to ViewController.
In prepare(for:sender:) of ExcerciseChooserViewController, grab a reference to destination view controller, downcast it to your subclass ViewController, and call your new method passing in the exerciseText string.
For example:
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var excerciseLabel: UIButton!
func changeText(_ text: String?) {
guard let text = text else { return }
excerciseLabel.setTitle(text, for: .normal)
print(excerciseLabel)
}
}
And in ExcerciseChooserViewController:
class ExcerciseChooserViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
private var chosenExercise: String?
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let excerciseChosen = "\(excercises[indexPath.row])"
print("You tapped cell number \(indexPath.row).")
print(excerciseChosen)
goBackToOneButtonTapped((Any).self)
Excercises.excChosen = excerciseChosen
print(Excercises.excChosen!)
chosenExercise = excerciseChosen
}
#IBAction func goBackToOneButtonTapped(_ sender: Any) {
performSegue(withIdentifier: "unwindToViewController", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationVC = segue.destination as? ViewController {
destinationVC.changeText(chosenExercise)
}
}
}

TableView is not appending new data

I am trying to create a simple app which lets appending data into an array in first ViewController and then send it to the SecondViewController through the segue. However instead of appending new data it is updating first index of an array.
import UIKit
class ViewController: UIViewController {
var data = [String]()
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func sendButton(_ sender: Any) {
if textField.text != nil {
let activity = textField.text
data.append(activity!) //Data should be appended here.
performSegue(withIdentifier: "sendSegue", sender: self)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "sendSegue" {
let destVC: AppTableViewController = segue.destination as! AppTableViewController
destVC.newData = data
}
}
}
Here is my SecondTableViewController and it just update first index so I am just keep changing my first index instead of adding new values into it.
import UIKit
class AppTableViewController: UITableViewController {
var newData = [String]()
#IBOutlet var myTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return newData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = newData[indexPath.row]
return cell
}
}
Based on this link,
Please change the function name sendButton to btnSendButton.
So that it will run before prepare.
As of now sendButton is running after prepare. So the data is not appending.

TableView Only Updates after Screen Rotates

Image of the tableview
I have a tableview with a collection view in each cell, all linked to an array. Each collection view has tags, so when I have stuff in the array from the beginning, all tableview cells and collection view cells appear properly in the app. But when I add an element to the array in the app itself (I have a second view controller with the stuff to do that), it works but the new table view cell only appears after the screen rotates (really odd). I have tried adding an object of the view controller with the table view in the second view controller where I add an element to the array. Then in the second view controller in ViewWillDisappear, I reloadData() through that object like this:
var vc : ViewController? = ViewController()
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
vc?.listOfActs.reloadData()
}
But this results in an EXC_BAD_INSTRUCTION
Then I tried adding self.listOfActs.reloadData() in the prepareForSegue in the view controller with the table view just so that I could see that it at least refreshes the data at some point in time but even that doesn't work when I click on add scene a second time.
UPDATE: New MainViewController
This is the new first view controller with the table view. I renamed it and have implemented the method for adding to array and reloading. It kind of works if I use an if let on the reloadData but then I'm back to square one where it only updates when I rotate the screen. When I get rid of the if let so it can actually try to update the table view, it gives me a Fata error: unexpectedly found a nil while unwrapping.
class MainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
//The Table View
#IBOutlet var AddActButton: UIButton!
#IBOutlet weak var listOfActs: UITableView!
var sectionTapped : Int?
var indexitemTapped : Int?
override func viewDidLoad() {
super.viewDidLoad()
listOfActs.delegate = self
listOfActs.dataSource = self
}
//Table View Functions
func numberOfSections(in tableView: UITableView) -> Int {
return actsCollection.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "actCell", for: indexPath) as! ActCell
cell.setCollectionViewDataSourceDelegate(self, forSection: indexPath.section)
return cell
}
//Add To Table View
func addObjects(appendage: Act) {
actsCollection.append(appendage)
if let shit = listOfActs {
shit.reloadData()
}
}
//Header Cell
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cellHeader = tableView.dequeueReusableCell(withIdentifier: "headerCell") as! HeaderCell
cellHeader.headerName.text = actsCollection[section].actName
return cellHeader
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 40
}
}
//Scene Collection in Act Cell
extension MainViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return actsCollection[collectionView.tag].actScenes.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "sceneCell", for: indexPath) as! SceneCell
cell.sceneTitle.text = actsCollection[collectionView.tag].actScenes[indexPath.item].sceneTitle
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
sectionTapped = collectionView.tag
indexitemTapped = indexPath.item
performSegue(withIdentifier: "showDetail", sender: self)
}
//Segue Prepare
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
let detailsVC = segue.destination as! SceneDetailController
detailsVC.textToAppearInSceneName = actsCollection[sectionTapped!].actScenes[indexitemTapped!].sceneTitle
}
}
}
UPDATE:New second view controller, the one that adds to the array.
class AddActController: UIViewController, UITextFieldDelegate {
#IBOutlet var sceneLiveName: UILabel!
#IBOutlet var sceneNameTextField: UITextField!
#IBOutlet var sceneDescriptionTextField: UITextField!
#IBOutlet var AddSceneButton: UIButton!
#IBOutlet var cardBounds: UIView!
var newName: String? = ""
#IBOutlet var cardShadow: UIView!
var shit = MainViewController()
override func viewDidLoad() {
super.viewDidLoad()
sceneNameTextField.delegate = self
AddSceneButton.alpha = 0.0
cardBounds.layer.cornerRadius = 20.0
cardShadow.layer.shadowRadius = 25.0
cardShadow.layer.shadowOpacity = 0.4
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 0.2){
self.AddSceneButton.alpha = 1.0
}
}
#IBAction func exitButton(_ sender: UIButton) {
dismiss(animated: true, completion: nil)
}
#IBAction func addSceneButton(_ sender: UIButton) {
if newName == "" {
sceneLiveName.text = "Enter Something"
sceneNameTextField.text = ""
}
else {
let appendAct: Act = Act(actName: newName!, actTheme: "Action", actScenes: [Scene(sceneTitle: "Add Act", sceneDescription: "")])
shit.addObjects(appendage: appendAct)
dismiss(animated: true, completion: nil)
}
}
//MARK: textField
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let text: NSString = (sceneNameTextField.text ?? "") as NSString
let resultString = text.replacingCharacters(in: range, with: string)
sceneLiveName.text = resultString
newName = String(describing: (sceneLiveName.text)!.trimmingCharacters(in: .whitespacesAndNewlines))
return true
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
sceneNameTextField.resignFirstResponder()
return true
}
/*
// 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.
}
*/
}
Here is the class for the uitableviewcell that contains its own collection view.
class ActCell: UITableViewCell {
#IBOutlet fileprivate weak var sceneCollection: UICollectionView!
}
extension ActCell {
func setCollectionViewDataSourceDelegate<D: UICollectionViewDataSource & UICollectionViewDelegate>(_ dataSourceDelegate: D, forSection section: Int) {
sceneCollection.delegate = dataSourceDelegate
sceneCollection.dataSource = dataSourceDelegate
sceneCollection.tag = section
sceneCollection.reloadData()
}
}
And here is the model with the user's data including the acts and scenes.
struct Scene {
var sceneTitle: String
var sceneDescription: String
//var characters: [Character]
//var location: Location
}
struct Act {
var actName: String
var actTheme: String
var actScenes : [Scene] = []
}
var actsCollection : [Act] = [
Act(actName: "dfdsfdsfdsf", actTheme: "Action", actScenes: [Scene(sceneTitle: "Example Act", sceneDescription: "")])
]
Any help is greatly appreciated. Thank you to all.
So if I'm not mistaken I believe the viewDidLoad method gets call during screen rotations. So this explains why it update during so. Now to get it to update without rotating the device, I would add an observer in the notificationCenter to watch for any updates to the tableView then call a #selector to do the reloadData(). So here is an example of this. In the viewDidLoad method add
NotificationCenter.default.addObserver(self, selector: #selector(refreshTable), name: NSNotification.Name(rawValue: "load"), object: nil)
Then add the method refreshTable()
func refreshTable() {
listOfActs.reloadData()
}
This is basically how I handle keeping the tableView refreshed.
Well - viewDidLoad is loaded only for the first time controller loads his view (not sure about rotation).
If you really need - you can reload tableView in viewWillAppear - but I wouldn't do this.
Instead of
actsCollection.append(appendAct)
dismiss(animated: true, completion: nil)
create a method on the first controller like addObjectToList(appendAct) and in that method, just easily append object to your list array and reload tableView after adding.
You will be reloading tableView only when you really add something to your list and not every time controller appears, you also don't need notification observer.
EDIT - UPDATE
What is this?
if newName == "" {
sceneLiveName.text = "Enter Something"
sceneNameTextField.text = ""
}
else {
let appendAct: Act = Act(actName: newName!, actTheme: "Action", actScenes: [Scene(sceneTitle: "Add Act", sceneDescription: "")])
shit.addObjects(appendage: appendAct)
dismiss(animated: true, completion: nil)
}
I mean - what is shit.AddObjects? Shit is defined as tableView - but you have to call this method on instance of your controller.
Another thing - change your setup from sections == number of items with 1 row to be one section with number of rows == number of items. :)

how to perform segue to a VC with Container

see this gif
when I choose the city Med , it passed to the TableVC not to the FirstVC (MainVC)
can I do that ? segue to the mainVC with the data passed through
the container (TableVC) ?
here what I did so far
MainVC
Empty
TableVC
import UIKit
class passedViewController: UITableViewController {
#IBOutlet weak var passcelltow: UITableViewCell!
#IBOutlet weak var passcell: UITableViewCell!
var passedCity1 = "اختر المدينة الاولى"
var passedCity2 = "اختر المدينة الثانية"
override func viewDidLoad() {
super .viewDidLoad()
passcell.textLabel?.text = passedCity1
passcelltow.textLabel?.text = passedCity2
}
}
Table 1 with data to pass to the TableVC
import UIKit
class city2ViewController: UIViewController , UITableViewDelegate , UITableViewDataSource{
#IBOutlet weak var tableView: UITableView!
var city2 = ["RUH" , "Med" , "Jed"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return city2.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
print(indexPath.row)
cell.textLabel?.text = city2[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "show", sender: city2[indexPath.row])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let passing = segue.destination as! passedViewController
passing.passedCity2 = sender as! String
}
}
Table 2 is the same ..
commend error
0 1 2 Could not cast value of type 'UIViewController' (0x107a10288) to
'table_view_test_pass.passedViewController' (0x105dbfdf8). (lldb)
You can pass data via segues or protocols. Since you are using segues i will show you a complete example and how to do it the right way in Swift 3. Using only two ViewControllers.
Create two UITextFields in the main "ViewController".
Create a new view controller of type UIViewController call it "MainTabelViewController" and add a tableView in it. Select content Dynamic prototypes Style Grouped and create 1 prototype cell and add a UILabel to it for the city name. "Don't forget the put the cell identifier name". I called it "cell".
Add the delegates and data sources to the class and add its functions like in code.
Create a segue from the main view controller to the main table view controller. And create another segue the opposite direction. "Don't forget the put the segue identifier names" I called them "toCity" & "toMain"
Create a "CityTableViewCell" controller of type UITableViewCell and create an IBOutlet of UILabel type where you will save the city name in as a text.
Edit this part in the AppDelegate.swift To delete the city names saved using in the UserDefaults every time the app is launched. So i wont populate the UITextFields randomly every time.
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var userDefaults: UserDefaults!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
userDefaults = UserDefaults.standard
userDefaults.removeObject(forKey: "City One")
userDefaults.removeObject(forKey: "City Two")
return true
}
This is the ordinary main ViewController.swift where you have your UITextFields in. I distinguish which UITextField did the user click on using the tags. You need to add also the UITextFieldDelegate protocol to be able to use the the textFieldDidBeginEditing function. And i also save the selected city names using UserDefaults class to call them when user chooses the other city.
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet var cityOneLabel: UITextField!
#IBOutlet var cityTwoLabel: UITextField!
#IBOutlet var continueButton: UIButton!
var selectedCityOne = ""
var selectedCityTwo = ""
var userDefaults: UserDefaults!
override func viewDidLoad() {
super.viewDidLoad()
cityOneLabel.delegate = self
cityTwoLabel.delegate = self
cityOneLabel.tag = 1
cityTwoLabel.tag = 2
continueButton.isEnabled = false
}
override func viewDidAppear(_ animated: Bool) {
userDefaults = UserDefaults.standard
cityOneLabel.text = selectedCityOne
cityTwoLabel.text = selectedCityTwo
if selectedCityOne != "" {
userDefaults.set(selectedCityOne, forKey: "City One")
} else {
cityOneLabel.text = userDefaults.string(forKey: "City One")
}
if selectedCityTwo != "" {
userDefaults.set(selectedCityTwo, forKey: "City Two")
} else {
cityTwoLabel.text = userDefaults.string(forKey: "City Two")
}
if cityOneLabel.text != "" && cityTwoLabel.text != "" {
continueButton.isEnabled = true
} else {
continueButton.isEnabled = false
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func continueButtonAction(_ sender: UIButton) {
//Later on continue after selecting the cities
}
func textFieldDidBeginEditing(_ textField: UITextField) {
performSegue(withIdentifier: "toCity", sender: textField.tag)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toCity" {
guard let cityVC = segue.destination as? MainTableViewController else {
return
}
cityVC.selectedTextField = sender as! Int
}
}
}
In the CityTabelViewCell.swift add the IBOutlet UILabel for the city name.
import UIKit
class CityTableViewCell: UITableViewCell {
#IBOutlet var cityNameLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
For the MainTabelViewController.swift write this:
Here is where i create an array of strings to populate my table view UILabels with.
import UIKit
class MainTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var cityTabelView: UITableView!
var cityNamesArray = ["Cairo", "Alexandria", "Suez"]
var selectedTextField = Int()
var selectedCityName = ""
override func viewDidLoad() {
super.viewDidLoad()
cityTabelView.delegate = self
cityTabelView.dataSource = self
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CityTableViewCell
cell.cityNameLabel.text = cityNamesArray[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cityNamesArray.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedCityName = cityNamesArray[indexPath.row]
performSegue(withIdentifier: "toMain", sender: self)
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
var title = ""
if selectedTextField == 1 {
title = "City One"
} else if selectedTextField == 2 {
title = "City Two"
}
return title
}
// 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?) {
if segue.identifier == "toMain" {
guard let mainVC = segue.destination as? ViewController else {
return
}
if selectedTextField == 1 {
mainVC.selectedCityOne = selectedCityName
} else if selectedTextField == 2 {
mainVC.selectedCityTwo = selectedCityName
}
}
}
}
This is how my layout looks like. Try it. I just added a continue button too if the user will have to go to another UIViewController after selecting the two cities.
If you want to segue to MainVC, you should instantiate a view controller from that class in prepare for segue.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let passing = segue.destination as! ViewController
passing.passedCity2 = sender as! String
}
Change ViewController to whatever the name of your class is for MainVC.
If you want to go back to the Parent View, you should be using an unwind-segue.
For that you must create the unwind segue method in the Parent View like this
#IBAction func unwindSegueFromChild(segue: UIStoryboardSegue){
// This code executes when returning to view
}
And in your child view you must create the unwind segue ctrl+dragging
There a dropdown appears and you select unwindSegueFromChild
Once you've done that, you must assign the unwind segue an identifier and programmatically perform it like a normal segue.

Resources