I'm learning how to use CoreData. I have a tableView with an add button, which segues to a view with just a textfield and a save button. When the button is pressed, the string in the textfield is saved in a CoreData database:
#IBOutlet weak var name: UITextField!
#IBAction func onSave(sender: UIBarButtonItem) {
saveItem(name.text!)
self.dismissViewControllerAnimated(true, completion: nil)
}
private func saveItem(itemName: String)
{
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let entity = NSEntityDescription.entityForName("Item", inManagedObjectContext: managedContext)
let item = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedContext)
item.setPrimitiveValue(itemName, forKey: "name")
do {
try managedContext.save()
listItems.append(item)
}catch let error {
print(error)
}
The tableView file has the Controller class and a global array of NSManagedObject:
Here is the relevant code:
var listItems = [NSManagedObject]()//outside the class
override func viewWillAppear(animated: Bool) { //in the tableviewcontroller class
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let fetch = NSFetchRequest(entityName: "Item")
do {
let results = try managedContext.executeFetchRequest(fetch)
listItems = results as! [NSManagedObject]
//EDIT
for item in listItems {
print(item)
}
} catch let error {
print(error)
}
self.tableView.reloadData()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
let item = listItems[indexPath.row]
cell.textLabel?.text = item.valueForKey("name") as? String
return cell
}
The database is very simple: an Entity named Item with a name attribute
This, however, doesn't work. When I press the save button, no new rows are added. If I remove all the code in viewWillAppear but self.tableView.reloadData(), the rows are created but when I relaunch the app they aren't there.
Any help would be much appreciated.
Related
in my app I am trying to create a budgeting app. I have tried creating a custom tableView Cell, but when it is run in the simulator, the tableView is condensed and does not show up correctly. As well as this, I am trying to save and load data from Coredata. However when the viewController loads, the error "Fatal Error: Index Out of Range" appears. I have viewed many coreData tutorials and threads however none of them help with my issue.
I want my tableViews to show up like this: storyboard view
However they appear like this.
Here is my code for the tableView and core data.
Any help is appreciated, thank you!
ViewController
var goals: [NSManagedObject] = []
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return CGFloat(goals.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! AllGoalsTableViewCell
let goal = goals[1]
cell.titleLabel?.text = goal.value(forKey: "title") as? String
return cell
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//1
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
//2
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Goal")
//3
do {
goals = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
if !self.goals.isEmpty
{
tableView.reloadData()
}
}
addNewViewController
var goals: [NSManagedObject] = []
func save(title: String, goalAmount: Double, percent: Double, amountPerWeek: Double) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
//1
let managedContext = appDelegate.persistentContainer.viewContext
//2
let entity = NSEntityDescription.entity(forEntityName: "Goal", in: managedContext)!
let goal = NSManagedObject(entity: entity, insertInto: managedContext)
//3
goal.setValue(title, forKey: "title")
goal.setValue(goalAmount, forKey: "goalAmount")
goal.setValue(percent, forKey: "percent")
goal.setValue(amountPerWeek, forKey: "amountPerWeek")
//4
do {
try managedContext.save()
goals.append(goal)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
#IBAction func addNewAction(_ sender: Any)
//CoreData
self.save(title: title, goalAmount: goalAmount ?? 0.0, percent: percent, amountPerWeek: amountPerWeekFloat ?? 0.0)
print("saved")
}
I save data from MovieDetailViewController in coreData. I try to access coreData data from another controller. When I save the data to coreData and switch to another controller where I output the result in tableView, it doesn't show me the newly saved names there. When I delete the background and reopen it then it shows me the last saved data.
I need something similar to the tableView method tableView.reloadData().
class MovieDetailViewController: UIViewController {
var coreDataMovieName: [NSManagedObject] = []
var movie: Movie!
var detailsImageView: UIImage!
var movieName = ""
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = movie.title
//Detalebshi Photos gamochena
if let posterPath = movie.posterPath {
TMDBClient.downloadPosterImage(path: posterPath) { (data, error) in
guard let data = data else {
return
}
let image = UIImage(data: data)
self.imageView.image = image
self.detailsImageView = image
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Favorite")
do {
coreDataMovieName = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
func save(name: String) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Favorite", in: managedContext)!
let person = NSManagedObject(entity: entity, insertInto: managedContext)
person.setValue(name, forKeyPath: "movieName")
do {
try managedContext.save()
coreDataMovieName.append(person)
print("saved \(person)")
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
#IBAction func coreDataFavoritesBtnTapped(_ sender: Any) {
save(name: movieName)
}
}
A controller where I try to access Core Data:
class CoreDataFavoritesViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var movie: [NSManagedObject] = []
override func viewDidLoad() {
super.viewDidLoad()
title = "The List"
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
tableView.dataSource = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Favorite")
do {
movie = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
}
// MARK: - UITableViewDataSource
extension CoreDataFavoritesViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return movie.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let person = movie[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = person.value(forKeyPath: "movieName") as? String
return cell
}
}
You are appending Data after context save
coreDataMovieName.append(person)
do it before
try managedContext.save()
I have a list of tableviewcells loaded. And beside each of those cells is an 'Add To favorite' button. When that 'Add To favorite' is clicked the image on it will change and that changed image should be stored into coredata so that when the app is run again I can know which cell was favorited. For that this is what has been tried...
func favoriteBtnTapped(cell: HistoryTableViewCell) {
if segmentControl.selectedSegmentIndex == 2 {
favBtnTapFlag = true
if let indexPath = tableview?.indexPath(for: cell) {
let myFavMsg = messages1[indexPath.row]
let likedMsg = myFavMsg.historyData
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "FavoritedMessages", in: managedContext)
let category = NSManagedObject(entity: entity!, insertInto: managedContext)
category.setValue(likedMsg, forKeyPath: "favData")
//New image is set on the cell and it is saved to coredata here...
cell.favoriteButton.setImage(UIImage(named: "pin"), for: .normal)
let imageData = UIImageJPEGRepresentation((cell.favoriteButton.imageView?.image)!, 1)
category.setValue(imageData, forKey: "favImage")
do {
try managedContext.save()
self.favMessages.append(category as! FavoritedMessages)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
}
And it is fetched in viewWillAppear like so...
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext =
appDelegate.persistentContainer.viewContext
let fetchRequest =
NSFetchRequest<NSManagedObject>(entityName: "Messages")
let fetchRequest1 = NSFetchRequest<NSManagedObject>(entityName: "FavoritedMessages")
do {
messages1 = try managedContext.fetch(fetchRequest as! NSFetchRequest<NSFetchRequestResult>) as! [Messages]
favMessages = try managedContext.fetch(fetchRequest1 as! NSFetchRequest<NSFetchRequestResult>) as! [FavoritedMessages]
for result in favMessages as [FavoritedMessages] {
if let imageData = result.value(forKey: "favImage") as? NSData {
if let image = UIImage(data:imageData as Data) as? UIImage {
favoritedImage.image = image
}
}
}
tableview.reloadData()
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}}
Here favoritedImage.image = image I'm trying to assign the fetched image to an imageview variable and then assign it at cellForRow. But how that can be done I'm not sure...
You should not save image in coredata.
Save the model with favourite_status as 1. (0 => unfavourited and 1 => favourited ) in coredata. (Basically boolean value)
Load the image from app bundle based on favourite_status.
When loading cells also use favourite_status to load image.
/// Edited
func favoriteBtnTapped(cell: HistoryTableViewCell) {
//1. Get Entity from datasource(Array) using indexpath.row
//2. Favorite the entity by setting favaorite_status = 1
//3. Save to coredata also
//4. Reload Table view
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//1. Get Entity from datasource(Array) using indexpath= Array
//2. Set cell image based on favaorite_status. Use UIImage.init(name:””) (if favaorite_status == 0, imagename will be different)
}
I am making a list app and have a Coredata model with two attributes in it (One for item and one for details)
I can save and input data and it will be displayed correctly but when i close and reopen the app, everything has its own cell.
Any help guys?
Code For CellAtIndex
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell")! as UITableViewCell
let item = listItems[indexPath.row]
let detailed = detailedItems[indexPath.row]
cell.textLabel?.text = item.valueForKey("item") as? String
cell.detailTextLabel?.text = detailed.valueForKey("detail") as? String
return cell
}
Code For ViewWillAppear
verride func viewWillAppear(animated: Bool) {
//Load First Item
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let fetchrequest = NSFetchRequest(entityName: "ListEnt")
do{
let results = try managedContext.executeFetchRequest(fetchrequest)
listItems = results as! [NSManagedObject]
detailedItems = results as! [NSManagedObject]
}catch{
print("Error")
}
}
Here is the code for saving, I have a dialog box appear, with to text field, and each text field has its own save function
//Saving Items//
func saveItem(itmToSave: String){
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let entity = NSEntityDescription.entityForName("ListEnt", inManagedObjectContext: managedContext)
let item = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedContext)
item.setValue(itmToSave, forKey: "item")
do{
try managedContext.save()
listItems.append(item)
}catch{
print("Saving Main Item")
}
}
//Saving Details
func saveItem2(itmToSave2: String){
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let entity = NSEntityDescription.entityForName("ListEnt", inManagedObjectContext: managedContext)
let item2 = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedContext)
item2.setValue(itmToSave2, forKey: "detail")
do{
try managedContext.save()
detailedItems.append(item2)
}catch{
print("Saving deatil Item")
}
}
Pictures of the problem
How it looks when you input data (Correct way)
How it looks when you close and reopen the app
Cheers Guys
I'm trying to fetch data from core data with table view cells, but when the data is reloaded each item in the cell take separate cells, I hope you understand what I mean.
this is the code that I think it has an error
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//1
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
//2
let fetchRequest = NSFetchRequest(entityName: "ShopList")
//3
do {
let results =
try managedContext.executeFetchRequest(fetchRequest)
people = results as! [NSManagedObject]
let results2 =
try managedContext.executeFetchRequest(fetchRequest)
ids = results2 as! [NSManagedObject]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! itemsList
let shopList = people[indexPath.row]
cell.items!.text = shopList.valueForKey("name") as? String
let shopList2 = ids[indexPath.row]
cell.itemsId!.text = shopList2.valueForKey("logo") as? String
return cell
}
Try this if problem still exists please add code for numberOfRowsInSection in question
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//1
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
//2
let fetchRequest = NSFetchRequest(entityName: "ShopList")
//3
do {
let results =
try managedContext.executeFetchRequest(fetchRequest)
people = results as! [NSManagedObject]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! itemsList
let shopList = people[indexPath.row]
cell.items!.text = shopList.valueForKey("name") as? String
cell.itemsId!.text = shopList.valueForKey("logo") as? String
return cell
}