Beginner question on passing data between view controllers - swift

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

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]

Table Content disappears on Scroll in TableView with Custom Cell using Subview - Swift

I have a ViewController which uses multiple Subviews (HomeViewController, etc.) which can be selected via a Custom Tab Bar at the bottom of my app. Inside the HomeViewController there is a UIView containing a UITableView containing a Prototype Custom Cell with name and image.
import UIKit
class HomeViewController: UIViewController {
#IBOutlet weak var friendView: UITableView!
let friends = ["batman", "harsh", "ava", "sasha", "fatima", "alfred"]
override func viewDidLoad() {
super.viewDidLoad()
friendView.delegate = self
friendView.dataSource = self
friendView.allowsSelection = false
}
}
extension HomeViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return friends.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = friendView.dequeueReusableCell(withIdentifier: "customCell") as! CustomCell
let friend = friends[indexPath.row]
cell.avatarImg.image = UIImage(named: friend)
cell.nameLbl.text = friend
return cell
}
}
Custom cell:
import UIKit
class CustomCell: UITableViewCell {
#IBOutlet weak var friendView: UIView!
#IBOutlet weak var nameLbl: UILabel!
#IBOutlet weak var avatarImg: UIImageView!
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
}
}
When I start the app, everything looks just fine. However, when I start scrolling inside the table, all data suddenly disappears. All relations between storyboard and code should be just fine. I think it might have got something to do with my need of using a Subview.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tabBarView: UIView!
#IBOutlet weak var contentView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
Design.makeCornersRound(view: tabBarView, radius: 10.0)
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { (timer) in
self.switchToHomeViewController()
}
}
#IBAction func onClickTabBar(_ sender: UIButton) {
let tag = sender.tag
if tag == 1 {
switchToIncomingsViewController()
}
else if tag == 2 {
switchToSpendingsViewController()
}
else if tag == 3 {
switchToHomeViewController()
}
else if tag == 4 {
switchToSavingsViewController()
}
else if tag == 5 {
switchToSettingsViewController()
}
}
func switchToHomeViewController() {
guard let Home = self.storyboard?.instantiateViewController(withIdentifier: "HomeViewController") as? HomeViewController else { return }
contentView.addSubview(Home.view)
Home.didMove(toParent: self)
}
...
}
Reference to the tutorial I have been trying to implement: https://www.youtube.com/watch?v=ON3Z0PXSoVk
In this function:
func switchToHomeViewController() {
// 1
guard let Home = self.storyboard?.instantiateViewController(withIdentifier: "HomeViewController") as? HomeViewController else { return }
// 2
contentView.addSubview(Home.view)
// 3
Home.didMove(toParent: self)
// 4
}
At 1 you create an instance of HomeViewController
at 2 you add its view to cotentView
at 3 you call didMove() ... but that doesn't do anything because you haven't added the controller to your hierarchy
at 4 your Home instance goes away, so the code in that controller no longer exists
You need to add the controller as a child controller.
As a side note, use lowerCase for variable names:
func switchToHomeViewController() {
// create an instance of HomeViewController
guard let homeVC = self.storyboard?.instantiateViewController(withIdentifier: "HomeViewController") as? HomeViewController else { return }
// add it as a child view controller
self.addChild(homeVC)
// add its view
contentView.addSubview(homeVC.view)
// here you should either set the view's frame or add constraints
// such as:
homeVC.view.frame = contentView.bounds
// inform the controller that it moved to a parent controller
homeVC.didMove(toParent: self)
}

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

How to open View Controller in Table View with Button by using Objc in Swift?

Stackoverflow
I know how to make a button in the table view cells with website links, rate, mail, and many things. However, How could I open the view controller with the instantiateViewController in the #Objc func's statements?
For example.
Create a new Table View Cell folder called FeedBackButtonsTableViewCell
class FeedBackButtonsTableViewCell: UITableViewCell {
#IBOutlet weak var ButtonCells: UIButton!
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
}
}
Let create a new view controller folder called
class FeedbackViewController: UIViewController {
#IBOutlet weak var TableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.navigationItem.title = "Feedback"
}
}
add the extension to calling the view controller to UITableViewDataSource and UITableViewDelegate and create a obj func statements inside of the second FeedbackViewController with UITableViewDataSource and UITableViewDelegate under the cells.
extension FeedbackViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
if indexPath.row == 1 {
buttonCell = TableView.dequeueReusableCell(withIdentifier: "ButtonCells") as? FeedBackButtonsTableViewCell
buttonCell?.ButtonCells.addTarget(self,action: #selector(LearnMore),for: .touchUpInside)
buttonCell?.ButtonCells.tag = indexPath.row
return buttonCell!
}
#objc func LearnMore() {
// How could I write to open the view controller with UIButton in the Table View Cells?
}
}
Thank you for bring a kind of help! :)
Simple solution could be to use procol.
protocol CellActionDelegate{
func didButtonTapped(index: Int)
}
Now confirm the protocol in FeedbackViewController. Take index and actionDelegate properties in your UITableViewCell subclass.
class FeedBackButtonsTableViewCell: UITableViewCell{
var actionDelegate: CellActionDelegate?
var index: Int?
.....
// Take Action of UIButton here
#IBAction func more(_ sender: Any) {
if let delegate = self.actionDelegate{
delegate.didButtonTapped(index!)
}
}
}
Now in your FeedbackViewController set actionDelegate & Corresponding index in
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {}
you can open anotherView controller from func didButtonTapped(index: Int) definition .
extension FeedbackViewController:CellActionDelegate{
func didButtonTapped(index: Int) {
let storybord = UIStoryboard(name: "Main", bundle: nil)
guard let controller = storybord.instantiateViewController(withIdentifier: "AnotherControllerIdentfier") as? AnotherViewController else{
fatalError("Could not finc another view controller")
}
self.present(controller, animated: true, completion: nil)
}
}
#objc func LearnMore() {
let viewController = FeedbackDetailsViewController()// creation of viewController object differs depends on how you fetch the UI, means either you are using storyboard or xib or directly making ui in code.
self.navigationController?.pushViewController(viewController, animated: true)
}

Tab Bar Item hidden behind tableview / not being shown?

I have an empty view with a tab bar pictured below, when i load a routine a table appears containing the contents, however it seems to overlay the tab bar killing off app navigation. Its not sized in the storyboard to overlay it and its constraint locked to not do so, so im unsure why this is happening, pics of the issue and VC's code below:
VC Code:
import Foundation
import UIKit
import CoreData
class RoutineController: UIViewController, UITableViewDataSource, UITableViewDelegate {
// MARK: - DECLARATIONS
#IBAction func unwindToRoutine(segue: UIStoryboardSegue) {}
#IBOutlet weak var daysRoutineTable: UITableView!
#IBOutlet weak var columnHeaderBanner: UIView!
#IBOutlet weak var todaysRoutineNavBar: UINavigationBar!
#IBOutlet weak var addTOdaysRoutineLabel: UILabel!
let date = Date()
let dateFormatter = DateFormatter()
let segueEditUserExerciseViewController = "editExerciseInRoutineSegue"
//This is the selected routine passed from the previous VC
var selectedroutine : UserRoutine?
// MARK: - VIEWDIDLOAD
override func viewDidLoad() {
super.viewDidLoad()
setupView()
daysRoutineTable.delegate = self
daysRoutineTable.dataSource = self
view.backgroundColor = (UIColor.customBackgroundGraphite())
dateFormatter.dateStyle = .short
dateFormatter.dateFormat = "dd/MM/yyyy"
let dateStr = dateFormatter.string(from: date)
todaysRoutineNavBar.topItem?.title = dateStr + " Routine"
}
// MARK: - VIEWDIDAPPEAR
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.daysRoutineTable.reloadData()
self.updateView()
}
// MARK: - TABLE UPDATE COMPONENTS
private func setupView() {
updateView()
}
// MARK: - TABLE SETUP
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let count = self.selectedroutine?.userexercises?.count
{
print("exercises: \(count)")
return count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? TodaysRoutineTableViewCell else {
fatalError("Unexpected Index Path")
}
cell.backgroundColor = UIColor.customBackgroundGraphite()
cell.textLabel?.textColor = UIColor.white
configure(cell, at: indexPath)
return cell
}
// MARK: - VIEW CONTROLER ELEMENTS VISIBILITY CONTROL
fileprivate func updateView() {
var hasUserExercises = false
if let UserExercise = self.selectedroutine?.userexercises {
hasUserExercises = UserExercise.count > 0
}
addTOdaysRoutineLabel.isHidden = hasUserExercises
columnHeaderBanner.isHidden = !hasUserExercises
daysRoutineTable.isHidden = !hasUserExercises
}
// MARK: - SETTING DATA FOR A TABLE CELL
func configure(_ cell: TodaysRoutineTableViewCell, at indexPath: IndexPath) {
if let userExercise = selectedroutine?.userexercises?.allObjects[indexPath.row]
{
print("\((userExercise as! UserExercise).name)")
cell.todaysExerciseNameLabel.text = (userExercise as! UserExercise).name
cell.todaysExerciseRepsLabel.text = String((userExercise as! UserExercise).reps)
cell.todaysExerciseSetsLabel.text = String((userExercise as! UserExercise).sets)
cell.todaysExerciseWeightLabel.text = String((userExercise as! UserExercise).weight)
}
}
}
requested table constraints
Debug hierarchy
The Segue that sends the user back to the view that looses its tab bar
if segue.identifier == "addToTodaySegue" {
let indexPath = workoutTemplateTable.indexPathForSelectedRow
let selectedRow = indexPath?.row
print("selected row\(selectedRow)")
if let selectedRoutine = self.fetchedResultsController.fetchedObjects?[selectedRow!]
{
if let todaysRoutineController = segue.destination as? RoutineController {
todaysRoutineController.selectedroutine = selectedRoutine
}
}
}
I also feel perhaps the viewDidAppear code may cause the issue, perhaps the super class?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.daysRoutineTable.reloadData()
self.updateView()
Updated storyboard image
I suspect you need to embed your viewController in a UINavigationController.
Consider the following setup:
I suspect your setup is like the upper one:
TapBar -> ViewController -show segue-> ViewController
Which results in a hidden tapbar, like in your description:
While the bottom setup:
TapBar -> NavigationCntroller -rootView-> ViewController -show segue-> ViewController
results in:
which is what you want, how I understood.
Update
It's hard to see. The screenshot of your Storyboard is in pretty low resulution, but the segues look wrong. Double check them. A Segue of type show (e.g push) looks like this:
Also clear project and derived data. Segue type changes sometime are ignored until doing so.
Try calling this self.view.bringSubviewToFront(YourTabControl).
The previous suggestion should work. But the content at the bottom part of tableview will not be visible as the tabbar comes over it. So set the bottom constraint of tableview as the height of tabbar.