Pass data between view controllers in navigation controller - swift

So I have a Navigation Controller together with 2 view controllers. The first view controller shows a text label with some text in it, a text field to type in a list of ingredients for food and a button that should bring us to our second view controller that will make a list out of the input in the text field. The following code is for the first view controller:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var foodImage: UIImageView!
#IBOutlet weak var searchField: UITextField!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "goSegue"){
if (searchField.text != ""){
let listOfFood = segue.destination as! ListOfFoodViewController
listOfFood.commaString = searchField.text!
}
else if (searchField.text == ""){
let alertEmpty = UIAlertController(title: "Emptyness", message: "There is no input!", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "Understood", style: .default, handler: nil)
alertEmpty.addAction(defaultAction)
self.present(alertEmpty, animated: true, completion: nil)
}
}
}
In the storyboard I made a segue (which I called "goSegue") to the second view controller (which is called ListOfFoodViewController) using the button. My code for the second view controller is
import UIKit
class ListOfFoodViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var commaString = ""
var listOfTags = [String]()
let simpleTableIdentifier = "SimpleTableIdentifier"
//DATA SOURCE METHODS
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listOfTags.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: simpleTableIdentifier)
if (cell == nil) {
cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: simpleTableIdentifier)
}
cell?.textLabel?.text=listOfTags[indexPath.row]
return cell!
}
override func viewDidLoad() {
super.viewDidLoad()
self.commaString = ""
self.listOfTags = self.commaString.components(separatedBy: ",")
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
So the user has to input a comma separated list of words, and I make a listOfTags out of it and I put this in the table. So before the segue, the function prepare(for segue ...) should be called and in here I make an instance of my second view controller (which has a property commaString) and assign the input to commaString as follows
let listOfFood = segue.destination as! ListOfFoodViewController
listOfFood.commaString = searchField.text!
But when I run this program, everything works except that the tableview is empty! I do not know why does this not work. Does it have something to do with the fact that I am using a Navigation Controller?

This is because you clear it here in viewDidLoad
self.commaString = ""
so comment that line and declare commaString like this
var commaString = "" {
didSet {
self.listOfTags = self.commaString.components(separatedBy: ",")
}
}
//
self.tableView.delegate = self
self.tableView.dataSource = self

In ViewController, you are saying:
listOfFood.commaString = searchField.text!
In ListOfFoodViewController, you are saying:
self.commaString = ""
So whatever ViewController put into ListOfFoodViewController, ListOfFoodViewController immediately erases it, and we end up with an empty table.

Related

How to input the value from UI button in Teble View cell to the variable declared in Table View Controller?

I want to save the value of filled button(●) into the array "q.answer[indexPath.row]" about each question "q.question[indexPath.row]).
currentQuizButtonIndex is currently renewed every time when ◯ changes to ● by tapping. However, I have no idea how to save in to variable q which is declared in TableViewController.
View Controller display
Code about QuizCell.swift (TableCell which is about 5 buttons and UIlabel.)
import UIKit
import Foundation
protocol QuizCellDelegate {
func quizCellDidChangeCurrentButtonIndex(_ cell: QuizCell, index: Int)
}
class QuizCell: UITableViewCell {
var currentQuizButtonIndex: Int = 0 {
didSet {
let value = self.currentQuizButtonIndex
self.updateCurrentQuizButton(value)
if let delegate = self.delegate {
delegate.quizCellDidChangeCurrentButtonIndex(self, index: value)
}
}
}
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet var answerButtons: [UIButton]!
var delegate: QuizCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
//print("ここまできてるか確認")
// Initialization code
}
#IBAction func didTapQuizButton(_ sender: UIButton) {
if let index = self.answerButtons.firstIndex(of: sender){
self.currentQuizButtonIndex = index
delegate?.quizCellDidChangeCurrentButtonIndex(self, index: index)
print(index)
}
}
private func updateCurrentQuizButton(_ currentIndex: Int){
for (index, answerButton) in self.answerButtons.enumerated(){
if index == currentIndex {
answerButton.setTitle("●", for: .normal)
} else {
answerButton.setTitle("○", for: .normal)
}
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Code about View Controller
import UIKit
class AnswerQuizViewController: UIViewController, UITableViewDelegate {
var q: QuestionSeries!
#IBOutlet weak var quizTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
quizTableView.dataSource = self
quizTableView.delegate = self
// cell xibファイルを使うときは書く必要があるやつ。
// quizTableView.register(UINib(nibName: K.Cells.QuizCellNibName, bundle: nil), forCellReuseIdentifier: K.Cells.QuizCellIdentifier)
quizTableView.register(UINib(nibName: "QuizCell", bundle: nil), forCellReuseIdentifier: "QuizCellIdentifier")
// Do any additional setup after loading the view.
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
// override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// // Get the new view controller using segue.destination.
// // Pass the selected object to the new view controller.
//// if segue.identifier == K.Segue.checkResult {
//// let resultViewController = segue.destination as! ResultViewController
//// answerQuizViewController.q =
//// print(answerQuizViewController.q)
// }
}
// MARK: - quizTableViewのアレンジ
extension AnswerQuizViewController: UITableViewDataSource, QuizCellDelegate {
func quizCellDidChangeCurrentButtonIndex(_ cell: QuizCell, index: Int) {
if let indexPath = self.quizTableView.indexPath(for: cell){
self.q.question[indexPath.row].answer = index
print(index)
}else{
print("ここきてます")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return q.question.count
//print(q.question.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let question = q.question[indexPath.row]
let cell = quizTableView.dequeueReusableCell(withIdentifier: K.Cells.QuizCellIdentifier, for: indexPath) as! QuizCell
cell.questionLabel.text = question.text
// print(question.text)
return cell
}
}
It is also helpful if you have any idea of implementing this by alternative way.
Thanks.
How about you create a static array and store your data into that array.
when the button is tapped you can append it into that static array.
Create a new file. Just a basic "Swift file".
struct structName {
static var qArray: [String] = []
}
Then append data by:
structName.q.append()
Finally get your data trough:
structName.q[index]

How to add an action button to a custom XIB cell?

I want to create a custom XIB Table View Cell with an action button that can segue to a new view controller.
So far, I created a custom XIB Cell (TaskListCell) with a button (timeButton), and registered the TaskListCell to TaskListViewController. For tableView(_:cellForRowAt:), I added tag for timeButton and addTarget(_:action:for:) to respond when timeButton is tapped. However, I get this error message: Thread 1: "-[Sprints.TaskListCell pressedTimeButton:]: unrecognized selector sent to instance 0x105311560"
Here is the custom XIB cell file:
class TaskListCell: UITableViewCell {
#IBOutlet weak var nameField: UITextField!
#IBOutlet weak var timeButton: UIButton!
#IBAction func pressedTimeButton(_ sender: UIButton) {
}
override func awakeFromNib() {
}
override func setSelected(_ selected: Bool, animated: Bool) {
}
}
Here is the ViewController that displays the custom cells in a Table View:
class TaskListViewController: UIViewController {
// MARK: - Outlet Variables
#IBOutlet weak var taskList: SelfSizedTableView!
...
// MARK: - Instance Variables
var taskData = [TaskData]()
var taskCount: Int = 1
...
// MARK: - View Controller Methods
override func viewDidLoad() {
super.viewDidLoad()
// Register TaskListCell.xib file
let taskCellNib = UINib(nibName: "TaskCell", bundle: nil)
taskList.register(taskCellNib, forCellReuseIdentifier: "taskCell")
...
// Connect table view's dataSource and delegate to current view controller
taskList.delegate = self
taskList.dataSource = self
}
// MARK: - Action Methods
// Adds new cell in Table View
#IBAction func pressedAddTask(_ sender: UIButton) {
taskCount += 1
taskList.reloadData()
taskList.scrollToRow(at: IndexPath(row: taskCount-1, section: 0), at: .bottom, animated: true)
}
// MARK: - Methods
// Segues to SelectTime screen when timeButton is pressed
#objc func pressedTimeButton(_ sender: UIButton) {
let destination = SelectTimeViewController()
navigationController?.pushViewController(destination, animated: true)
}
}
extension TaskListViewController: UITableViewDataSource {
// Return the number of rows in table view
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return taskCount
}
// Return the cell to insert in table view
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "taskCell", for: indexPath) as! TaskListCell
// Fatal error for next line: "Unexpectedly found nil while implicitly unwrapping an Optional value"
cell.timeButton.tag = indexPath.row
cell.timeButton.addTarget(self, action: #selector(pressedTimeButton(_:)), for: .touchUpInside)
return cell
}
}
extension TaskListViewController: UITableViewDelegate {
}
Check instance of IBAction from your cell may be it is being called. Remove this function and it will work
#IBAction func pressedTimeButton(_ sender: UIButton) {
}

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
}
}

Trouble with segue/unsegue when making a list app

I'm new to coding, so please bear with me. I was following an online tutorial that worked with plists to make a habit list app. I have a table view controller that shows a list of habits and a segue that presents modally a view controller that has text fields to add a habit.
enter image description here
Every time it runs, nothing happens when I click on the "save" and "cancel" buttons. I realize this is a vague question as it doesn't pinpoint to a specific issue, but I am really struggling with fixing this issue and would really appreciate if someone proofreads the code. The app builds and runs with no warnings.
This is the table view controller that shows the habits:
class TableViewController: UITableViewController {
//MARK: Properties
var habits = [Habit]()
//MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return habits.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {
fatalError("The dequeued cell is not an instance of ViewController.")
}
// Fetches the appropriate habit for the data source layout.
let habit = habits[indexPath.row]
cell.textLabel?.text = habit.mainGoal
cell.detailTextLabel?.text = habit.microGoal
return cell
}
#IBAction func unwindToHabitList(sender: UIStoryboardSegue) {
if let source = sender.source as?ViewController, let habit = source.habit {
//add a new habit
let newIndexPath = IndexPath(row: habits.count, section: 0)
habits.append(habit)
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
}
This is the view controller that adds a habit:
class ViewController: UIViewController, UITextFieldDelegate, UINavigationControllerDelegate {
#IBOutlet weak var saveButton: UIBarButtonItem!
#IBOutlet weak var mainGoalTextField: UITextField!
#IBOutlet weak var microGoalTextField: UITextField!
var habit: Habit?
//method for configuring controller before presenting
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
//configure this destination view controller only when save button is pressed
guard let button = sender as? UIBarButtonItem, button === saveButton else {
os_log("save button was not pressed, cancelling", log: OSLog.default, type: .debug)
return
}
let mainGoal = mainGoalTextField.text ?? ""
let microGoal = microGoalTextField.text ?? ""
//set the habit to be passed on to tableViewController after the unwind segue
habit = Habit(mainGoal: mainGoal, microGoal: microGoal)
}
#IBAction func cancel(_ sender: UIBarButtonItem) {
// Depending on style of presentation (modal or push presentation), this view controller needs to be dismissed in two different ways.
let isPresentingInAddHabitMode = presentingViewController is UINavigationController
if isPresentingInAddHabitMode {
dismiss(animated: true, completion: nil)
}
else if let owningNavigationController = navigationController{
owningNavigationController.popViewController(animated: true)
}
else {
fatalError("The ViewController is not inside a navigation controller.")
}
}
I appreciate any and all help in advance!
STORYBOARD CONNECTIONS:
TABLEVIEW CONTROLLER CONNECTIONS
ADD HABIT VIEW CONTROLLER CONNECTIONS

Object in array is replaced when trying to append it via prepareForSegue

When I run my code, everything runs fine without errors. However, when I try to add a cell to my table view using an add button, a segue, and a text field, I am unable to do so. What happens is that when I append the object (custom) to the array that is used to create the cells, the array is emptied and filled again. The result of this is just having one cell at the table view at one time. The code is below..
Routine.swift:
struct Routine {
var name: String?
var desc: String?
}
RoutineListViewController.swift:
class RoutineListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var routines = [Routine]()
override func viewDidLoad() {
super.viewDidLoad()
println("There are \(routines.count) routine(s).")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return routines.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("RoutineCell", forIndexPath: indexPath) as UITableViewCell
var cellName = routines[indexPath.row].name!
cell.textLabel?.text = cellName
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
}
EditRoutineViewController:
class EditRoutineViewController: UIViewController {
var newRoutine = Routine(name: "", desc: "")
var routineName: String?
var routineDescription: String?
var allTextFields: UITextField?
#IBOutlet weak var nameField: UITextField!
#IBOutlet weak var descriptionField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
allTextFields = textField
if (textField.placeholder == "Name") {
if (textField.text != nil) {
routineName = textField.text
println("Routine name set as '\(routineName)'")
}
textField.resignFirstResponder()
return true
} else if (textField.placeholder == "Description") {
if (textField.text != nil) {
routineDescription = textField.text
println("Routine description set as '\(routineDescription!)'")
}
textField.resignFirstResponder()
return true
}
return true
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let navController = segue.destinationViewController as UINavigationController
let RoutineListController = navController.topViewController as RoutineListViewController
println("There are \(RoutineListController.routines.count) routines in the routines array.")
RoutineListController.routines.append(newRoutine)
println("There are \(RoutineListController.routines.count) routines in the routines array.")
println("A new routine called \(newRoutine.name!) has been added to the routines array.")
}
#IBAction func cancel(sender: AnyObject) {
allTextFields?.endEditing(true)
self.dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func done(sender: AnyObject) {
routineName = self.nameField.text
routineDescription = self.descriptionField.text
if (self.nameField.text == "") {
// Display alert view
let nameAlert = UIAlertController(title: "Oops!", message:
"Make sure you enter a name for the routine", preferredStyle: UIAlertControllerStyle.Alert)
nameAlert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil))
self.presentViewController(nameAlert, animated: true, completion: nil)
} else {
println("A routine called \(self.routineName!) is about to be created.")
if (self.routineDescription != nil) {
println("It has a description of '\(self.routineDescription!)'.")
}
var localRoutine = Routine(name: routineName, desc: routineDescription?)
self.newRoutine = localRoutine
view.endEditing(true)
self.performSegueWithIdentifier("showRoutines", sender: nil)
}
}
}
I'd venture a guess that the problem lies in the code in EditRoutineViewController which saves the new routine and exits/navigates back to the RoutineViewController view. Namely, with this piece of code:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let navController = segue.destinationViewController as UINavigationController
let RoutineListController = navController.topViewController as RoutineListViewController
RoutineListController.routines.append(newRoutine)
}
you create a new instance of RoutineViewController and push it on the navigation stack. Since RoutineViewController starts with an empty Routine array, you just add the single, newly-created item. And so on, and so on (I can't see from your code how you call the edit window from the RoutineViewController, but it seems it works like this).
If this is indeed the case, then you'll need to change the application flow a little bit. So, I'm presuming that the app starts from the RoutineViewController view, which should then modally show EditRoutineViewController as soon as you tap on the 'Add' or '+' button. EditRoutineViewController should also define a protocol (let's call it EditRoutineDelegate) with two methods:
protocol EditRoutineDelegate {
func didSaveRoutine(routine: Routine)
func didCancelEnteringRoutine()
}
You'd need to modify the routine-saving routine (chuckle chuckle) to something like this:
} else {
var localRoutine = Routine(name: routineName, desc: routineDescription?)
self.newRoutine = localRoutine
view.endEditing(true)
self.delegate.didSaveRoutine(self.newRoutine)
}
So, now, when you enter a new routine in EditRoutineViewController it calls its delegate, RoutineViewController to execute the adding of the new routine and refreshing of the table view. The implementation could be something like this:
func didSaveRoutine(routine: Routine) {
routines.append(routine)
tableView.reloadData()
navigationController.dismissViewControllerAnimated(true, completion: nil)
}
And the didCancelEnteringRoutine can just dismiss the modal window.
I'd be glad to elucidate any details I failed to mention.
PS: I presume you have working knowledge on how delegation works. If not, it would be a great idea to spend some time learning about the concept, since it's a widely used pattern in Cocoa.
EDIT: Here's a small demo project which has the functionality you need. I hope it will be clearer this way.