Confusing myself extracting data from core data to two different view controllers - ios

I'm new to Core Data and think I may be making things a little harder on myself than it needs to be, because I seem to be repeating code across View Controllers. I have successfully managed to populate a table with my core data entries with this code:
import UIKit
import CoreData
class ClientListViewController: UIViewController, UITabBarDelegate, UITableViewDataSource {
var clientItems : [Client] = []
#available(iOS 2.0, *)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return clientItems.count
}
#available(iOS 2.0, *)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as UITableViewCell
let items = clientItems[indexPath.row]
cell.textLabel?.text = items.name
cell.accessoryType = UITableViewCellAccessoryType.disclosureIndicator
return cell
}
func getData() {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do {
clientItems = try context.fetch(Client.fetchRequest())
} catch {
print("Fetch Failed")
}
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
if editingStyle == .delete {
let item = clientItems[indexPath.row]
context.delete(item)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do {
clientItems = try context.fetch(Client.fetchRequest())
} catch {
print("Fetch Failed")
}
}
tableView.reloadData()
}
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
navigationController?.navigationBar.prefersLargeTitles = true
}
override func viewWillAppear(_ animated: Bool) {
getData()
tableView.reloadData()
}
}
This code displays a clients name in the table as expected but when I click on the the cell I want it to load up my new view showing all the clients info stored for them in core data. To start I want the navigation bar title to be the persons name that was tapped from the cell on the previous View Controller but my code always shows the first entry because I'm saying [0] how can the data provided be from the actual row selected in the table on the other View Controller.
I'm confused and seem to be repeating unnecessary code in the process. Here is my second View Controllers code:
import UIKit
import CoreData
class ClientViewController: UIViewController {
var clientItems : [Client] = []
#IBOutlet weak var nameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
getData()
navigationItem.title = String(describing: clientItems[0].name!)
print(clientItems[0].name!)
}
func getData() {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do {
clientItems = try context.fetch(Client.fetchRequest())
} catch {
print("Fetch Failed")
}
}
}

Related

How can I get an array to an NSManagedObject

I am sorry if I put out silly questions but I am new to Swift. I am building an app and so far it goes quite well. But now I want to delete some rows from my tableview which gets feeded from an Array with Strings. And then I want to save/fetch that using core data. I believe I have the code for it but the problem is that I am trying to save an array full of Strings. so I get error message saying: Cannot convert value of type 'String' to expected argument type 'NSManagedObject'. And therefore I am wondering, how can I solve this? Is there any way to "add those Strings to an NSManagedObject somehow?
Here are my code:
the array :
and here are the code:
import UIKit
import CoreData
class tableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (List.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
cell.textLabel?.text = List.self[indexPath.row]
cell.textLabel?.textColor = UIColor.white
cell.backgroundColor = UIColor.clear
return(cell)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
if editingStyle == .delete{
let rows = List[indexPath.row]
context.delete(rows)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do{
List = try context.fetch(MealsMenu.fetchRequest()) as! [String]
}
catch{
print(error)
}
}
myTableView.reloadData()
}
#IBOutlet weak var myTableView: UITableView!
override func viewDidAppear(_ animated: Bool) {
myTableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
CoredataClass.saveItems()
CoredataClass.loadData()
}
}
You will need to create a function like this
Imagine that your Entity is Recipe which has an string attribute recipeName which contains the name of the recipe.
func fetchPersistentData() -> [String] {
var recipes = [String]()
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return recipes
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<Recipe>(entityName: "Recipe")
do {
let fetchedResults = try managedContext.fetch(fetchRequest)
if fetchedResults.count > 0 {
recipes = fetchedResults.map { ("\($0.recipeName)")}
return recipes
}
} catch let error as NSError {
// Wrong handling
print(error.description)
}
return recipes
}

pass data from a table view Controller to View Controller using data stored in core data

I'm a little newbie and I have a doubt, I have a TableViewController and another ViewController that I have as a detailViewController, what I try to do is that when a cell is selected in the tableview, it presents the corresponding data stored in core data for that cell in the detailViewcontroller.
This is the file that controls the tableViewcontroller :
import UIKit
class CostumerTableViewController: UITableViewController {
var costumerArray:[Costumer] = []
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.reloadData()
self.fetchData()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return costumerArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let name = costumerArray[indexPath.row]
cell.textLabel?.text = name.costumerName!
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
if editingStyle == .delete {
let costumerDelete = costumerArray[indexPath.row]
context.delete(costumerDelete)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do {
costumerArray = try context.fetch(Costumer.fetchRequest())
} catch {
print(error)
}
}
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let Storyboard = UIStoryboard(name: "Main", bundle: nil)
let DvC = Storyboard.instantiateViewController(withIdentifier: "costumerDetailViewController") as! costumerDetailViewController
let n = costumerArray[indexPath.row]
let Cn = n.costumerName!
DvC.getCostumerName = Cn
self.navigationController?.pushViewController(DvC, animated: true)
}
func fetchData() {
// se crea el context
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do { // se hace el request del array
costumerArray = try context.fetch(Costumer.fetchRequest())
} catch {
print(error)
}
}
}
In the compilation does not give me any problem, some everything goes well the problem is that it does not present anything in the detail viewController label that in this case I try to send the data from this code.
This is the detailViewController code :
import UIKit
class costumerDetailViewController: UIViewController {
var getCostumerName = String()
#IBOutlet weak var labelName: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
labelName.text! = getCostumerName
}
}
First Check Cn has value or "" on this line.
let Cn = n.costumerName
Change your code in class costumerDetailViewController for declare getCostumerName
var getCostumerName = "" //for avoid crash. if value goes nil.
Use Print() in viewDidLoad and debug it.
Hope this will help you.

Referencing core data attribute from declared variable

I'm following a swift development course for beginners and am trying to make a very simple app that creates new tasks with an entered text once a button is pressed, but I am encountering a few errors that I can't seem to understand.
The errors happen in my ViewController and the editor tells me my Core Data Entity does not possess an attribute named "corename" while it very well does.
Here is a screenshot of the errors : 3 errors
And here is my code :
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var tasks : [Taskentity] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillAppear(_ animated: Bool) {
//Get the data from Core data
getData()
//Reload the table view
tableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return tasks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath : IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let task = tasks[indexPath.row]
if (task.isImportant == true){
cell.textLabel?.text = "😅 \(tasks.corename!)"
} else {
cell.textLabel?.text = tasks.corename!
}
return cell
}
func getData() {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do {
tasks = try context.fetch(Taskentity.fetchRequest())
} catch {
print("Fetching Data")
}
}
}
Tasks is a Array of Taskentities, you probably meant to access task.corename not tasks.corename
if (task.isImportant == true){
cell.textLabel?.text = "😅 \(task.corename!)"
} else {
cell.textLabel?.text = task.corename!
}
And for the TableViewDelegate problem, just make sure to implement all necessary funcs... You are missing 1:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}

How to retrieve data corresponding to a particular UITableViewCell from core data?

I'm doing a project in swift 3.0 and I have two UIViewControllers and one of them has three text fields. In that UIViewController once the data is entered in the textfields and when the save button is pressed the data will be saved to core data. In my core data module, the name of my entity is "Task" and it has got four attributes namely: amount, age, name and isImportant(all are strings except the last one). The code is shown below
import UIKit
class AddTaskViewController: UIViewController {
var isRowTapped :Bool?
#IBOutlet weak var textFiels: UITextField!
#IBOutlet weak var firstTextField: UITextField!
#IBOutlet weak var secondTextField: UITextField!
#IBOutlet weak var isImp: UISwitch!
override func viewDidLoad() {
}
#IBAction func buttonTabbed(_ sender: AnyObject) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let task = Task(context: context)
task.name = textFiels.text
task.age = firstTextField.text
task.amount = secondTextField.text
task.isImportant = isImp.isOn
(UIApplication.shared.delegate as! AppDelegate).saveContext()
print("Data saved successfully")
navigationController!.popViewController(animated: true)
}
}
Even though three values will be saved to core data, only the attribute called "name" will be taken out from core data and will be printed on cells in the second view controller where i have my table view (it is more like an identity name). What I want to know is since I'm entering three values initially to core data in the initial view controller, once a particular row is selected in the second view controller how do I get all the three values to correspond to that name attribute and re-assign them to my text fields in the initial view controller so that I could edit them and save again. The code of my second view controller is as below (tableviewViewController).
import UIKit
class DispalyViewController: UIViewController, UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableview: UITableView!
var tasks : [Task] = []
var isRowSelected :Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
loadData()
self.tableview.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func loadData(){
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do {
tasks = try context.fetch(Task.fetchRequest())
print("fetch sucess")
}catch{
print("fetch failed")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("count is : \(tasks.count)")
return tasks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell ()
print("table view loaded")
let task = tasks[indexPath.row]
if task.isImportant{
cell.textLabel?.text = "😌 \(task.name!)"
}else{
cell.textLabel?.text = task.name
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "ShowNxtVC", sender: nil)
//isRowSelected = true
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let selectedRow = segue.destination as! AddTaskViewController
//selectedRow.isRowTapped = isRowSelected
}
//For swipe access allow
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
if editingStyle == .delete {
let task = tasks [indexPath.row]
context.delete(task)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do {
tasks = try context.fetch(Task.fetchRequest())
}catch{
print("fetchinh failed")
}
}
tableview.reloadData()
}
}

Populate the data saved on core data on a tableView

The code of my first UIViewController looks like this and basically I want to save the data whatever I type on the UITextField and retrieve it and populate on a UITableView. code as below. (I think my issue is in the cellForRow method)
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var textField1: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func nxtbuttonpressed(_ sender: AnyObject) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
//Lets save data
let newUser = NSEntityDescription.insertNewObject(forEntityName: "Expenses", into: context)
newUser.setValue(textField1.text, forKey: "expenseName")
do {
try context.save ()
print("data saved successfully")
}catch{
print("There was an error")
}
performSegue(withIdentifier: "ShowNxtVC", sender: nil)
}
}
This is where I retrieve the data that I saved in the above VC (as the code bellow). However even though I can print all the data on the console, only the last data item I saved will populate to the UITableViewCell. How can i overcome this as to populate all the data that I've saved before instead the one that i saved last.
import UIKit
import CoreData
class TableViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var myArray : Array? = []
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest <NSFetchRequestResult> (entityName: "Expenses")
request.returnsObjectsAsFaults = false
do {
let results = try context.fetch(request)
// check data existance
if results.count>0 {
print(results.count)
for resultGot in results as! [NSManagedObject]{
if let expName = resultGot.value(forKey:"expenseName") as? String{
myArray = [expName]
print("my array is : \(myArray)")
}
}
}
}catch{
print("No Data to load")
}
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(myArray?.count)
return myArray!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell ()
cell.textLabel?.text = myArray? [indexPath.row] as! String?
return cell
}
}
first off all you have to change this:
myArray = [newValue] because that resets your array with just one value every time it finds a new expense in core data.
var array = [String]()
array = ["hello"]
array = ["joe"]
print(array)
// returns: ["joe"]
use:
myArray.append(newExpense)
and you get:
var array2 = [String]()
array2.append("hello")
array2.append("joe")
print(array2)
// returns: ["hello", "joe"]
After the FOR loop you add:
tableView.reloadData()
Now you should be up and running
There is another thing you should do:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ExpCell", for: indexPath)
cell.textLabel?.text = myArray?[indexPath.row] as! String
return cell
}
In your Storyboard you need to put ExpCell as reuse identifier into the TableViewCell. The dequeReusableCell command loads only the cells you can see on your device and reuses those cells after you scrolled them out of sight. This way your app uses much less memory and will be faster.
update table view with data, after myArray = [expName] use
DispatchQueue.main.async { [unowned self] in
self.tableView.reloadData()
}
change the tableview cell for row at index path function that will solve your problem
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier identifier: String,
for indexPath: IndexPath)
if(!cell){
cell = tableView.register(tableViewCellClass,forCellReuseIdentifier identifier: String)
}
return cell
}

Resources