I'm trying to set a label depending on the UserDefault value but It's crashing with the error:
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file
So, first time I run it the app does NOT crash using this code:
MainActivity
override func viewDidLoad() {
super.viewDidLoad()
getAllVehicles()
}
func getAllVehicles(){
let defaults = UserDefaults.standard
if defaults.object(forKey: "currentCar") != nil{
currentCar = defaults.string(forKey: "currentCar")
registrationPlate.text = currentCar
}
}
So the application is setting the label to it's UserDefault and that does work, but when I'm trying to change it (Just to mention, it's from another class/viewcontroller)
ChooseCarViewController
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let car = carsArray[indexPath.row]
let defaults = UserDefaults.standard
defaults.set(car, forKey: "currentCar")
if let storyboard = storyboard{
let vc = storyboard.instantiateViewController(withIdentifier: "MainViewController") as! MainViewController
vc.getAllVehicles()
self.dismiss(animated: true)
}
}
So by any reason I can NOT change the name when calling the method.
I've also tried to set it directly by using
vc.registrationPlate.text = car
Just to mention:
I've tried to print out "car" and it does return the correct value.
I've also tried to print the "currentCar" in MainActivity after calling the method and It's returning the correct car. But the textfield cannot be changed because of unwrapping an Optional value.
All UI stuff that you build with storyboard are optional and they are implicitly unwrapped using !. For example
#IBOutlet weak var registrationPlate: UILabel! // <- Do you see the `!` here?
So as UIViewController default template suggests, you should
Do any additional setup after loading the view.
The first function you can do it in is viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
If you initialize a view controller from another one it doesn't grantees that all outlets are automatically have been assigned (yet).
First of all you must use the initializer from the storyboard or from the xib file to have the correct initializer set the outlets.
Then you should wait for viewDidLoad. (You can use vc.loadView() to force it, but it's not recommended at all)
In your code, you are using storyboard.instantiateViewController... that creates another instance and since you are just dismissing the view, it's not needed at all! Because the previous controller is already loaded and it exist in the memory.
And note that you are using reading the registrationPlate inside getAllVehicles and it should be done after view is loaded. So you can extract this part from it and move it somewhere after view gets loaded.
Do not misuse UserDefaults to share data between controllers, pass the data directly.
The crash occurs because the outlet is not connected yet when the label is accessed.
The solution is to create a temporary variable in MainActivity and assign the value to the label in viewDidLoad
var currentCar = ""
override func viewDidLoad() {
super.viewDidLoad()
registrationPlate.text = currentCar
}
Assign the car to the temporary variable in ChooseCarViewController and you have to present the vc
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let car = carsArray[indexPath.row]
UserDefaults.standard.set(car, forKey: "currentCar")
if let vc = storyboard?.instantiateViewController(withIdentifier: "MainViewController") as? MainViewController {
vc.currentCar = car
present(vc, animated: true, completion: nil)
self.dismiss(animated: true)
}
}
You want to use the protocol / delegate pattern.
Define a protocol:
protocol ChangeCarDelegate: AnyObject {
func changeCar(_ newCar: String)
}
Have your Main view controller "conform" to that protocol:
class MainViewController: UIViewController, ChangeCarDelegate {
func changeCar(_ newCar: String) {
currentCar = newCar
registrationPlate.text = currentCar
}
}
When you present the Choose Car VC, assign its ChangeCarDelegate property:
#IBAction func showChooseCar(_ sender: Any) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "ChooseCarVC") as? ChooseCarViewController {
vc.changeCarDelegate = self
present(vc, animated: true, completion: nil)
}
}
Or, if you are using a Storyboard created Segue to present the Choose Car VC:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? ChooseCarViewController {
vc.changeCarDelegate = self
}
}
When you select a car from the table, tell the delegate of the new selection:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let car = carsArray[indexPath.row]
let defaults = UserDefaults.standard
defaults.set(car, forKey: "currentCar")
// communicate with the delegate
changeCarDelegate?.changeCar(car)
dismiss(animated: true, completion: nil)
}
Here is a full set of classes for a simple "Main" VC with a label and a button. Tapping the button presents "ChooseCar" VC, which has a table of cars.
protocol ChangeCarDelegate {
func changeCar(_ newCar: String)
}
class MainViewController: UIViewController, ChangeCarDelegate {
#IBOutlet var registrationPlate: UILabel!
var currentCar: String = ""
override func viewDidLoad() {
super.viewDidLoad()
getAllVehicles()
}
func getAllVehicles(){
let defaults = UserDefaults.standard
if defaults.object(forKey: "currentCar") != nil{
currentCar = defaults.string(forKey: "currentCar") ?? ""
registrationPlate.text = currentCar
}
}
#IBAction func showChooseCar(_ sender: Any) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "ChooseCarVC") as? ChooseCarViewController {
vc.changeCarDelegate = self
present(vc, animated: true, completion: nil)
}
}
// or, if you are using a Storyboard created Segue to present the Choose Car VC
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? ChooseCarViewController {
vc.changeCarDelegate = self
}
}
func changeCar(_ newCar: String) {
currentCar = newCar
registrationPlate.text = currentCar
}
}
class ChooseCarViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
weak var changeCarDelegate: ChangeCarDelegate?
#IBOutlet var tableView: UITableView!
var carsArray: [String] = [
"Ford",
"Honda",
"Toyota",
"Chrysler",
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return carsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CarCell", for: indexPath)
cell.textLabel?.text = carsArray[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let car = carsArray[indexPath.row]
let defaults = UserDefaults.standard
defaults.set(car, forKey: "currentCar")
changeCarDelegate?.changeCar(car)
dismiss(animated: true, completion: nil)
}
}
As you have mentioned this in your comments!!
#TejaNandamuri So how can that be fixed? Using ViewDidAppear?
But how comes it's not loaded since the ViewController is still in the back stack right?
In this code, vc is not pointing to the same view controller which is behind in the stack. It is creating a new view controller. In new vc, textfield is not yet loaded.
if let storyboard = storyboard{
let vc = storyboard.instantiateViewController(withIdentifier: "MainViewController") as! MainViewController
vc.getAllVehicles()
self.dismiss(animated: true)
}
Use delegates to achieve what you want.
You are trying to assigning a nullable value to label, handle null using guard let or if let
like below
guard let currentCar = defaults.string(forKey: "currentCar") else{
registrationPlate.text = ""
return
}
registrationPlate.text = currentCar ?? ""
Related
I am confused to why my table view is not reloading after i call the tableView.reloadData() function. Here is what you should know about my project. The initial view controller is a tableViewController and when you click the add button in the navigation bar it pulls up presents the "addItemViewController". It is presented overCurrentContext. Everything to this point works fine, but the part that doesn't work is when you fill out the info in the pop up I created and push the button to save it it saves to the core data fill but when i reload it it doesnt even call that. When i close the app and reload it the data shows up but it doesnt show up when i add it and call the same function.
import UIKit
import CoreData
protocol reloadTableView: class {
func reloadTableView()
}
class TableViewController: UITableViewController {
//Global Variables
let addItemVC = AddItemController()
var itemArray = [Item]()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
super.viewDidLoad()
addItemVC.delegate = self
loadItems()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Constants.cellIdentifier, for: indexPath) as! Cell
let array = itemArray[indexPath.row]
cell.dateCreated.text = array.dateCreated
cell.workoutLabel.text = array.workoutName
cell.weightLifted.text = array.weight
return cell
}
//MARK: - Add Button Pressed
#IBAction func addItemPressed(_ sender: UIBarButtonItem) {
let storyboard = UIStoryboard(name: "AddItem", bundle: nil)
let addItemVC = storyboard.instantiateViewController(identifier: "AddItemController")
addItemVC.isModalInPresentation = true
addItemVC.modalPresentationStyle = .overCurrentContext
addItemVC.modalTransitionStyle = .crossDissolve
addItemVC.navigationController?.isNavigationBarHidden = true
present(addItemVC, animated: true, completion: nil)
}
//MARK: - Create and Load Functions
func saveData() {
do {
try context.save()
} catch {
print("Error Saving Data \(error)")
}
tableView.reloadData()
}
func loadItems() {
let request: NSFetchRequest<Item> = Item.fetchRequest()
do {
itemArray = try context.fetch(request)
} catch {
print("error")
}
tableView.reloadData()
}
}
//MARK:// - Add Item Vc Delegate
extension TableViewController: reloadTableView {
func reloadTableView() {
do {
try context.save()
} catch {
print("Error Saving Data \(error)")
}
let request: NSFetchRequest<Item> = Item.fetchRequest()
do {
itemArray = try context.fetch(request)
} catch {
print("error")
}
tableView.reloadData()
print("There are", itemArray.count, "in the item array")
print(itemArray.last?.workoutName)
//the print statement are not showing up in console
}
}
and the second file
import UIKit
import CoreData
class AddItemController: UIViewController {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var delegate: reloadTableView?
#IBOutlet weak var viewContainer: UIView!
#IBOutlet weak var exercise: UITextField!
#IBOutlet weak var weight: UITextField!
#IBOutlet weak var reps: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addMaxPressed(_ sender: UIButton) {
if exercise.text != "" && weight.text != "" && reps.text != "" {
let newItem = Item(context: context)
let formatter = DateFormatter()
newItem.dateCreated = formatter.formattedDate()
newItem.weight = weight.text
newItem.reps = reps.text
newItem.workoutName = exercise.text
dismiss(animated: true) {
self.delegate?.reloadTableView()
}
}
}
#IBAction func exitPressed(_ sender: UIButton) {
dismiss(animated: true, completion: nil)
}
}
//MARK: - UITextField func
extension AddItemController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.view.endEditing(true)
return true
}
}
You're setting the delegate on the wrong instance of the add item view controller.
You create one instance with...
let addItemVC = AddItemController()
...and another with...
let addItemVC = storyboard.instantiateViewController(identifier: "AddItemController")
You set the delegate on the first of those, but present the second. That means when you get to...
self.delegate?.reloadTableView()
...of the presented controller, nothing happens.
If you're not using the first instance, get rid of it and set the delegate in the same section where you set the presentation style, etc.
When you put ? after an optional, it means you don't want to know whether it did what you asked or not. Obviously, you do want to know so you should test the value instead and print a message if the value isn't what you expect.
I have two VC, the first is a tableView, the second is the detailedView VC where you can add a new item to the tableView.
I have implemented passing data forward with segues (from plus button in the first VC) and backwards with delegate and protocol when adding a new item to the tableView (triggered when tapping a save button on the second VC).
I added a segue from the prototype cell to the second VC (detailed view), I have also managed to test in the first VC which segue is triggered, ie: add new item or go to the detailedView of that item. the problem I'm facing, the save button in the second VC no longer works (and the cancel button also), I want to be able to edit the text fields in the second VC and hit the save button to save the edited item back in the first one.
I found a way to implement it with unwind segues, however I would like to know how to do it with delegate ?
My first VC code:
class ThingsTableViewController: UITableViewController, CanReceive {
var myThings = [Thing]()
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myThings.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = myThings[indexPath.row].name
cell.detailTextLabel?.text = myThings[indexPath.row].type
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "addNewThing" {
let secondVC = segue.destination as! UINavigationController
let ThingsViewController = secondVC.topViewController as! ThingsViewController
ThingsViewController.delegate = self
} else if segue.identifier == "showDetail" {
guard let thingDetailViewController = segue.destination as? ThingsViewController else {fatalError("Unknown Destination")}
guard let selectedCell = sender as? UITableViewCell else {
fatalError("Unexpected sender: \(sender)")
}
guard let indexPath = tableView.indexPath(for: selectedCell) else {
fatalError("The selected cell is not being displayed by the table")
}
let selectedThing = myThings[indexPath.row]
thingDetailViewController.thing = selectedThing
}
}
func dataReceived(data: Thing) {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
myThings[selectedIndexPath.row] = data
tableView.reloadRows(at: [selectedIndexPath], with: .none)
} else {
myThings.append(data)
tableView.reloadData()
}
}
the code in the second vc look like:
protocol CanReceive {
func dataReceived(data: Thing)
}
}
class ThingsViewController: UIViewController, UITextFieldDelegate {
var delegate : CanReceive?
var thing : Thing?
#IBOutlet weak var thingNameTextField: UITextField!
#IBOutlet weak var thingTypeTextfield: UITextField!
#IBAction func saveThingButton(_ sender: UIBarButtonItem) {
let newThing = Thing(name: thingNameTextField.text!, type: thingTypeTextfield.text!)
delegate?.dataReceived(data: newThing)
self.dismiss(animated: true, completion: nil)
self.navigationController?.popViewController(animated: true)
}
#IBAction func cancelButton(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: nil)
self.navigationController?.popViewController(animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
thingNameTextField.delegate = self
updateSaveButtonState()
if let thing = thing {
navigationItem.title = thing.name
thingNameTextField.text = thing.name
thingTypeTextfield.text = thing.type
}
}
// MARK: UITextField Delegate
// get triggered when the user hit the return key on the keyboard
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
thingNameTextField.resignFirstResponder()
self.navigationItem.rightBarButtonItem?.isEnabled = true
return true
}
//gives chance to read info in text field and do something with it
func textFieldDidEndEditing(_ textField: UITextField) {
updateSaveButtonState()
navigationItem.title = thingNameTextField.text
}
func updateSaveButtonState() {
let text = thingNameTextField.text
self.navigationItem.rightBarButtonItem?.isEnabled = !text!.isEmpty
}
}
You're setting delegate for case that segue's identifier is addNewThing, but what about case that identifier is showDetail?
Set delegate of segue's destination for case that segue's identifier is showDetail
if segue.identifier == "addNewThing" {
...
} else if segue.identifier == "showDetail" {
...
thingDetailViewController.delegate = self
...
}
Then when you need to dismiss ViewController embed in navigation controller, just dismiss it and then dismiss navigation controller
In ThingsViewController class, please define delegate with weak var
weak var delegate: CanReceive?
One more issue is observed,
Looks like your instance name and class name are same, please update the instance name,
if segue.identifier == "addNewThing" {
let secondVC = segue.destination as! UINavigationController
let thingsVC = secondVC.topViewController as! ThingsViewController
thingsVC.delegate = self
} else if segue.identifier == "showDetail" {
guard let thingDetailViewController = segue.destination as?
ThingsViewController else {fatalError("Unknown Destination")}
guard let selectedCell = sender as? UITableViewCell else {
fatalError("Unexpected sender: \(sender)")
}
guard let indexPath = tableView.indexPath(for: selectedCell) else {
fatalError("The selected cell is not being displayed by the table")
}
let selectedThing = myThings[indexPath.row]
thingDetailViewController.thing = selectedThing
thingDetailViewController.delegate = self
}
Your tableView.reloadData() should happen in main queue
func dataReceived(data: Thing) {
myThings.append(data)
DispatchQueue.main.async {
tableView.reloadData()
}
}
Declare a protocol for receiving data.
protocol ViewControllerDelegate: class {
func didTapButton(with data: Int)
}
declare a delegate of protocol where you are sending the data
class SecondVC: UIViewController {
weak var delegate: ViewControllerDelegate?
#IBAction func buttonPressed(_ sender: UIButton) {
delegate?.didTapButton(with: sender.tag)
}
}
confirm to the protocol where you want to receive the data and make the delegate to self.
class FirstVC : UIViewController,ViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
func gotoSecond() {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "identifier") as! SecondVC
vc.delegate = self
self.navigationController?.pushViewController(vc, animated: true)
}
func didTapButton(with data: Int) {
print(data)
}
}
I'm going from a tab bar controller view to another view thats not part of the tab bar controller. When, I try and go back from the view to the tab bar controller view, by pressing the back button, the tab bar doesn't show. The code for the button is under back_golf. The view isn't in a navigational controller
import UIKit
import Firebase
class Golf: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableview_golf: UITableView!
var array = [String]()
var ref : DatabaseReference!
var handle: DatabaseHandle!
#IBAction func back_golf(_ sender: Any) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let navigationController = appDelegate.window?.rootViewController as!
UINavigationController
navigationController.dismiss(animated: true, completion: nil)
//self.navigationController?.popToRootViewController(animated: true)
//self.performSegue(withIdentifier: "seque_golf", sender: nil)
//hidesBottomBarWhenPushed = false
}
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
handle = ref?.child("Girls_golf").observe(.childAdded, with: {
(snapshot) in
if let item = snapshot.value as? String {
self.array.append(item)
self.tableview_golf.reloadData()
}
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section:
Int) -> Int {
return array.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
let cell = tableview_golf.dequeueReusableCell(withIdentifier:
"golf_cell")! as UITableViewCell
cell.textLabel?.text = array[indexPath.row]
cell.textLabel?.numberOfLines = 0
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
It seems that you have poped over the ViewControllers (in this example "Golf"). If this is true, this should be worked for:
#IBAction func back_golf(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
My Tabbed Bar and Navigation bar disappears whenever I use a Show Detail Segue to pass data through to another ViewController. I have managed to pass the relevant data in order to read which cell has been selected so I can insert a .txt file into a UITextField the the final ViewController. It's sort of like a dynamic textView. I would still like to be able to select different Tabs when I am in the article view (final vc). Is there any way to keep the NavBar and Tabbed View Bar in the final viewController after the segue, or is there a way to pass data through a Nav Controller. Or, sorry this will be the last one, do I use the prepare for segue and push the previous NavController (not too sure how to do this). Sorry I couldn't post more than 2 image links, hence the large amount of code. Thanks
Image 1: Layout of the Storyboard
This is from rBVC.class (My Road Block Category Class File), where I have a TableView:
class rBVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var rBTableView: UITableView!
var rBLabels = ["General Info", "Drunk Driving", "Arrest", "Searches"]
var rBIcons = [UIImage(named: "generalInfo"), UIImage(named: "drunkDriving"), UIImage(named: "arrests"), UIImage(named: "searches")]
var valueToPass: String!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return rBIcons.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let rBCell:UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "rBCell", for: indexPath) as! rBCell
rBCell.textLabel!.text = self.rBLabels[indexPath.row]
rBCell.imageView!.image = self.rBIcons[indexPath.row]
return rBCell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You selected cell #\(indexPath.row)!")
//performSegue(withIdentifier: "rBASegue", sender: self)
print(valueToPass)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "rBSegue"){
/*let vcName = "rBTXT"
let vC = storyboard?.instantiateViewController(withIdentifier: vcName)
self.navigationController?.pushViewController(vC!, animated: true)*/
let indexPath = self.rBTableView.indexPathForSelectedRow!
let currentCell = self.rBTableView.cellForRow(at: indexPath) as! rBCell
valueToPass = currentCell.textLabel?.text
self.rBTableView.deselectRow(at: indexPath, animated: true)
// initialize new view controller and cast it as your view controller
let viewController = segue.destination as! rBAV
// your new view controller should have property that will store passed value
viewController.passedValue = valueToPass
print("\(valueToPass)test")
}
}
}
Image 4: This is where I have the problem of the disappearing bar's.
class rBAV: UIViewController {
#IBOutlet var rBTV: UITextView!
var passedValue: String?
var rBArticle: String = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if passedValue == "General Info"{
rBArticle = "rBGI"
}
if passedValue == "Drunk Driving"{
rBArticle = "rBDD"
}
if passedValue == "Arrest"{
rBArticle = "rBA"
}
if passedValue == "Searches"{
rBArticle = "rBS"
}
// Save data to file
let fileName = "Test"
let DocumentDirURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let fileURL = DocumentDirURL.appendingPathComponent(fileName).appendingPathExtension("txt")
//print("FilePath: \(fileURL.path)")
/*** Read from project txt file ***/
// File location
let fileURLProject = Bundle.main.path(forResource: rBArticle, ofType: "txt")
// Read from the file
var readStringProject = ""
do {
readStringProject = try String(contentsOfFile: fileURLProject!, encoding: String.Encoding.utf8)
} catch let error as NSError {
print("Failed reading from URL: \(fileURL), Error: " + error.localizedDescription)
}
rBTV.insertText(readStringProject)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I was following a YouTube tutorial on how to create a to-do list with CoreData and my app can build and run however instead of using another view controller to create a task, I created a modal view controller to be displayed over the regular view controller. The problem is it saves it to the CoreData but only displays when the app is reset, this is all the code used for the regular view controller where the tasks should appear:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableViewTest: UITableView!
var tasks : [Task] = []
override func viewDidLoad() {
super.viewDidLoad()
tableViewTest.dataSource = self
tableViewTest.delegate = self
self.navigationController?.isNavigationBarHidden = true
}
override func viewWillAppear(_ animated: Bool) {
getData()
tableViewTest.reloadData()
}
func tableView(_ tableViewTest: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasks.count
}
func tableView(_ tableViewTest: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let task = tasks[indexPath.row]
cell.textLabel?.text = task.name!
return cell
}
func getData() {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do {
tasks = try context.fetch(Task.fetchRequest())
}
catch {
print("Fetch Error")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
and this is the code for the modal view controller where the user enter is information to be saved to CoreData:
class popVCAdd: UIViewController {
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var popViewAc: UIView!
override func viewDidLoad() {
super.viewDidLoad()
popViewAc.layer.cornerRadius = 20
popViewAc.layer.masksToBounds = true
let toolbar = UIToolbar()
toolbar.sizeToFit()
textField.inputAccessoryView = toolbar
let keyboardDone = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.done, target: self, action: #selector(self.disappearKey))
toolbar.setItems([keyboardDone], animated: false)
}
#IBAction func doneBtn(_ sender: Any) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let task = Task(context: context)
task.name = textField.text!
(UIApplication.shared.delegate as! AppDelegate).saveContext()
}
#IBAction func dismissPop(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
func disappearKey() {
view.endEditing(true)
}
}
Does anybody know what's wrong with it?
Please Change you ModalPresantaion Style to Full Screen
See Below Screen Shot:
Select Segue First:
Change Its Presantation Style to Full Screen:
I am Suggesting you above changes because:
viewWillAppear of your ViewController is not calling after Dismissing from your popVCAdd Controller.