Save user input data of UITableView - ios

There is an option in my app to add new excercises (via user input), i've managed to set the alert and add feature, but I can't save the data added and let it be persistent after closure and that.
//
// ExcerciseListViewController.swift
// OneRepMax
//
// Created by Mauro Garcia on 17/01/2019.
// Copyright © 2019 Mauro Garcia. All rights reserved.
//
import Foundation
import UIKit
import CoreData
class ExcerciseListViewController: UIViewController, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
title = "Excercises"
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
// This view controller itself will provide the delegate methods and row data for the table view.
tableView.delegate = self
tableView.dataSource = self
}
#IBOutlet weak var tableView: UITableView!
// Add a new excercise
#IBAction func addExcercise(_ sender: UIBarButtonItem) {
let alert = UIAlertController(title: "New Excercise", message: "Add a new excercise", preferredStyle: .alert)
let saveAction = UIAlertAction(title: "Save", style: .default) { [unowned self] action in
guard let textField = alert.textFields?.first,
let nameToSave = textField.text
else {
return
}
print(nameToSave)
self.excercises.append(nameToSave)
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
alert.addTextField()
alert.addAction(saveAction)
alert.addAction(cancelAction)
present(alert, animated: true)
}
var excercises: [String] = []
}
// MARK: - UITableViewDataSource
extension ExcerciseListViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
print(excercises.count)
return excercises.count
}
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
let cell =
tableView.dequeueReusableCell(withIdentifier: "Cell",
for: indexPath)
cell.textLabel?.text = excercises[indexPath.row]
return cell
}
// What happens when user touch an excercise
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let excerciseChosen = "\(excercises[indexPath.row])"
goBackToOneButtonTapped((Any).self)
excUserChose = excerciseChosen
print(excUserChose)
print("You tapped cell number \(indexPath.row).")
print(excerciseChosen)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "changeText"), object: nil)
}
func goBackToOneButtonTapped(_ sender: Any) {
performSegue(withIdentifier: "unwindToViewController", sender: self)
}
}
// TODO: save user input
In my AppDelegate.swift
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "OneRepMax")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
I want to -when the user press "Save"- to save the data to be accesible after.
I've tried to implement CoreData but I have failed trying. Don't know what I'm doing wrong.
EDIT:
Code for adding and saving input
var excercisess: [NSManagedObject] = []
#IBAction func addExcercise(_ sender: UIBarButtonItem) {
let alert = UIAlertController(title: "New Excercise", message: "Add a new excercise", preferredStyle: .alert)
let saveAction = UIAlertAction(title: "Save", style: .default) { [unowned self] action in
guard let textField = alert.textFields?.first,
let nameToSave = textField.text
else {
return
}
self.save(name: nameToSave)
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
alert.addTextField()
alert.addAction(saveAction)
alert.addAction(cancelAction)
present(alert, animated: true)
}
func save(name: String) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
/*1.
Before you can save or retrieve anything from your Core Data store, you first need to get your hands on an NSManagedObjectContext. You can consider a managed object context as an in-memory “scratchpad” for working with managed objects.
Think of saving a new managed object to Core Data as a two-step process: first, you insert a new managed object into a managed object context; then, after you’re happy with your shiny new managed object, you “commit” the changes in your managed object context to save it to disk.
Xcode has already generated a managed object context as part of the new project’s template. Remember, this only happens if you check the Use Core Data checkbox at the beginning. This default managed object context lives as a property of the NSPersistentContainer in the application delegate. To access it, you first get a reference to the app delegate.
*/
let managedContext = appDelegate.persistentContainer.viewContext
/*
An NSEntityDescription object is associated with a specific class instance
Class
NSEntityDescription
A description of an entity in Core Data.
Retrieving an Entity with a Given Name here person
*/
let entity = NSEntityDescription.entity(forEntityName: "Excercise", in: managedContext)!
/*
Initializes a managed object and inserts it into the specified managed object context.
init(entity: NSEntityDescription,
insertInto context: NSManagedObjectContext?)
*/
let excercises = NSManagedObject(entity: entity, insertInto: managedContext)
//we can simply create person object this way also.
// let excercise = Excercise(context: managedContext)
/*
With an NSManagedObject in hand, you set the name attribute using key-value coding. You must spell the KVC key (name in this case) exactly as it appears in your Data Model
*/
excercises.setValue(name, forKeyPath: "name")
/*
You commit your changes to person and save to disk by calling save on the managed object context. Note save can throw an error, which is why you call it using the try keyword within a do-catch block. Finally, insert the new managed object into the people array so it shows up when the table view reloads.
*/
do {
try managedContext.save()
excercisess.append(excercises)
tableView.reloadData()
print("SAVED")
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
And the code for fetching:
func fetchAllExcercises(){
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
/*Before you can do anything with Core Data, you need a managed object context. */
let managedContext = appDelegate.persistentContainer.viewContext
/*As the name suggests, NSFetchRequest is the class responsible for fetching from Core Data.
Initializing a fetch request with init(entityName:), fetches all objects of a particular entity. This is what you do here to fetch all Person entities.
*/
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Excercise")
/*You hand the fetch request over to the managed object context to do the heavy lifting. fetch(_:) returns an array of managed objects meeting the criteria specified by the fetch request.*/
do {
excercisess = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}

There are several things that you still need once you have the xcdatamodeld file set up.
Your array should be an array of your CoreData entity.
var exercises = [Exercise]()
You need to create an instance of the managed object context.
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
Then you will need to use that instance to create new objects in the array, save the new objects and fetch them. Have a go at finding out how to create those functions (Youtube, web articles) and then see if you still have questions.

Related

How to update CoreData data using Swift

In myscenario, I am storing popupVC textfield title and descript values into CoreData. Once, stored Data into CoreData then fetching data into UITableView. Each and every Tableview cell I am having edit button within editActionsForRowAt. Once, user clicked the edit button I am passing the EditpopupVC.task = self.userData[indexPath.row]. In EditpopupVC I am receiving the values like var task: NSManagedObject? = nil. Now, After modification of data if user click update button then I am calling func update(title: String, descript: String) { } for passed particular value update.
Edit Button Click to Passing data from ListViewController to EditpopupVC
override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let editAction = UITableViewRowAction(style: .default, title: "Edit", handler: { (action, indexPath) in
print("Edit tapped")
let task = self.userData[indexPath.row]
let EditpopupVC = self.storyboard?.instantiateViewController(withIdentifier: "editviewcontroller") as! EditViewController
EditpopupVC.titlename = "Edit"
EditpopupVC.task = self.userData[indexPath.row]
EditpopupVC.modalTransitionStyle = .crossDissolve
EditpopupVC.modalPresentationStyle = .overFullScreen
self.present(EditpopupVC, animated: true, completion: nil)
})
return [editAction]
}
Once User Modified Text Data then Update button click to Calling Update Function
update(title: Titletextfield.text ?? "Empty", descript: Descriptiontextview.text)
CoreData Update Function I am using Below
func update(title: String, descript: String) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managedObjectContext = appDelegate.persistentContainer.viewContext
task.title = title
task.descript = descript
do {
try managedObjectContext.save()
} catch {
print("Could not save. \(error)")
}
}
EditpopupVC.task = self.userData[indexPath.row] This line helping to pass the array of Data to EditpopupVC.
Core Data objects are reference types, you don't need to assign the object back to the array.
The crash occurs because you don't set tasks in the popupVC so it remains empty and raises a out-of-range exception.
In the main controller declare updateTitles with a more specific type
var updateTitles = [Myrecord]()
In popupVC delete tasks, declare task as
var task : Myrecord!
and replace update with
func update(title: String, descript: String) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managedObjectContext = appDelegate.persistentContainer.viewContext
task.title = title
task.descript = descript
do {
try managedObjectContext.save()
self.dismiss(animated: true, completion: nil)
} catch {
print("Could not save. \(error)")
}
}
the index path is not needed.
When the controller disappears reload the table view.
Side note:
Force unwrapping AppDelegate is fine. Without AppDelegate the application won't even launch.

iOS 9 and iOS 10 CoreData simultaneously

I have a question about Swift 3 releted with Core Data. I am developing an App in Swift with Xcode 8, and I need to support iOS 9 and iOS10. The problem is that I don't know how to get the AppDelegate and the Context (to use for store and get data from my Entities). I think my code should be something like this:
#if avaliable(iOS10,*)
{
// iOS 10 code
} else
{
// iOS 9 code
}
But I don't know what to do.
Any idea?
(A small help in correction would be appreciated)
# Code in Swift 3 for Core Data for both iOS 9 and iOS 10 #
Since you want the core data code for both iOS 9 and iOS 10 then you don't have to use NSPersistentContainer as it not supported in iOS 9, so you have to use old method instead
If at the time of project creation you haven't included core data and later you want to include it, follow the following steps :-
Step 1. Go to Build Phases -> Link Binary with Library -> click on + sign -> Add CoreData.framework
Step 2. Now got to file -> New File -> select Data Model.
Step 3. Now you need to write some code inside AppDelegate.swift to get set go :-
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
self.saveContext()
}
// MARK: - Core Data stack
lazy var applicationDocumentsDirectory: NSURL = {
// The directory the application uses to store the Core Data store file. This code uses a directory named "hacker.at.work.mTirgger" in the application's documents Application Support directory.
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return urls[urls.count-1] as NSURL
}()
lazy var managedObjectModel: NSManagedObjectModel = {
// The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
let modelURL = Bundle.main.url(forResource: "Model", withExtension: "momd")!
return NSManagedObjectModel(contentsOf: modelURL)!
}()
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
// The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
// Create the coordinator and store
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.appendingPathComponent("SingleViewCoreData.sqlite")
var failureReason = "There was an error creating or loading the application's saved data."
do {
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
} catch {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject?
dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject?
dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
abort()
}
return coordinator
}()
lazy var managedObjectContext: NSManagedObjectContext = {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
// MARK: - Core Data Saving support
func saveContext () {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
abort()
}
}
}
}
That's all your Core Data is ready for both iOS 9 and iOS 10 in Swift 3. Enjoy !!!
If you want to support iOS9 then the best bet is not to use any new iOS10 stuff.
Stick with the iOS9 API for now, and in the future when you eventually drop iOS9 support you can refactor your code to use the newer methods then.
edit: Just to be clear, all the iOS9 code will run fine on iOS10. You don't need to do anything special there, it is all backwards compatible.
CoreData for iOS9 and iOS10 simultaneously
For who you may need it:
This is an unmodified Master-Detail App template from the latest Xcode7 (for iOS9) release of May 2016. I have updated it with the current latest Xcode (Version 8.2.1)
With this template you can develope an application that uses CoreData compatible with iOS9 and iOS10, at least for now (May 2017)
AppDelegate.swift
//
// AppDelegate.swift
// trash
//
// Created by Markus on 22/05/17.
// Copyright © 2017 Markus. All rights reserved.
//
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let splitViewController = self.window!.rootViewController as! UISplitViewController
let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
splitViewController.delegate = self
let masterNavigationController = splitViewController.viewControllers[0] as! UINavigationController
let controller = masterNavigationController.topViewController as! MasterViewController
controller.managedObjectContext = self.managedObjectContext
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates.
self.saveContext()
}
// MARK: - Split view
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false }
if topAsDetailController.detailItem == nil {
// Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
return true
}
return false
}
// MARK: - Core Data stack
lazy var applicationDocumentsDirectory: URL = {
// The directory the application uses to store the Core Data store file. This code uses a directory named "com.senbei.trash" in the application's documents Application Support directory.
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return urls[urls.count-1]
}()
lazy var managedObjectModel: NSManagedObjectModel = {
// The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
let modelURL = Bundle.main.url(forResource: "trash", withExtension: "momd")!
return NSManagedObjectModel(contentsOf: modelURL)!
}()
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
// The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
// Create the coordinator and store
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.appendingPathComponent("SingleViewCoreData.sqlite")
var failureReason = "There was an error creating or loading the application's saved data."
do {
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
} catch {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject?
dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject?
dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
abort()
}
return coordinator
}()
lazy var managedObjectContext: NSManagedObjectContext = {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
// MARK: - Core Data Saving support
func saveContext () {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
abort()
}
}
}
}
MasterViewController.swift
//
// MasterViewController.swift
// trash
//
// Created by Markus on 22/05/17.
// Copyright © 2017 Markus. All rights reserved.
//
import UIKit
import CoreData
class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate {
var detailViewController: DetailViewController? = nil
var managedObjectContext: NSManagedObjectContext? = nil
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.leftBarButtonItem = self.editButtonItem
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:)))
self.navigationItem.rightBarButtonItem = addButton
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
}
override func viewWillAppear(_ animated: Bool) {
self.clearsSelectionOnViewWillAppear = self.splitViewController!.isCollapsed
super.viewWillAppear(animated)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func insertNewObject(_ sender: AnyObject) {
let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
let newManagedObject = NSEntityDescription.insertNewObject(forEntityName: entity.name!, into: context)
// If appropriate, configure the new managed object.
// Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
newManagedObject.setValue(Date(), forKey: "timeStamp")
// Save the context.
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
//print("Unresolved error \(error), \(error.userInfo)")
abort()
}
}
// MARK: - Segues
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let object = self.fetchedResultsController.object(at: indexPath)
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
// MARK: - Table View
override func numberOfSections(in tableView: UITableView) -> Int {
return self.fetchedResultsController.sections?.count ?? 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionInfo = self.fetchedResultsController.sections![section]
return sectionInfo.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let object = self.fetchedResultsController.object(at: indexPath) as! NSManagedObject
self.configureCell(cell, withObject: object)
return cell
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let context = self.fetchedResultsController.managedObjectContext
context.delete(self.fetchedResultsController.object(at: indexPath) as! NSManagedObject)
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
//print("Unresolved error \(error), \(error.userInfo)")
abort()
}
}
}
func configureCell(_ cell: UITableViewCell, withObject object: NSManagedObject) {
//cell.textLabel!.text = object.value(forKey: "timeStamp")!.description
cell.textLabel!.text = (object.value(forKey: "timeStamp")! as AnyObject).description
}
// MARK: - Fetched results controller
//var fetchedResultsController: NSFetchedResultsController {
var fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult> {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
//let fetchRequest = NSFetchRequest()
//let fetchRequest = NSFetchRequest<Event>(entityName: "Event") //another alternative
let fetchRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Event")
// Edit the entity name as appropriate.
let entity = NSEntityDescription.entity(forEntityName: "Event", in: self.managedObjectContext!)
fetchRequest.entity = entity
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "timeStamp", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: "Master")
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
do {
try _fetchedResultsController!.performFetch()
} catch {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
//print("Unresolved error \(error), \(error.userInfo)")
abort()
}
return _fetchedResultsController!
}
//var _fetchedResultsController: NSFetchedResultsController? = nil
var _fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>?
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
switch type {
case .insert:
self.tableView.insertSections(IndexSet(integer: sectionIndex), with: .fade)
case .delete:
self.tableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade)
default:
return
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
tableView.insertRows(at: [newIndexPath!], with: .fade)
case .delete:
tableView.deleteRows(at: [indexPath!], with: .fade)
case .update:
self.configureCell(tableView.cellForRow(at: indexPath!)!, withObject: anObject as! NSManagedObject)
case .move:
tableView.moveRow(at: indexPath!, to: newIndexPath!)
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.endUpdates()
}
/*
// Implementing the above methods to update the table view in response to individual changes may have performance implications if a large number of changes are made simultaneously. If this proves to be an issue, you can instead just implement controllerDidChangeContent: which notifies the delegate that all section and object changes have been processed.
func controllerDidChangeContent(controller: NSFetchedResultsController) {
// In the simplest, most efficient, case, reload the table view.
self.tableView.reloadData()
}
*/
}
DetailViewController.swift
//
// DetailViewController.swift
// trash
//
// Created by Markus on 22/05/17.
// Copyright © 2017 Markus. All rights reserved.
//
import UIKit
class DetailViewController: UIViewController {
#IBOutlet weak var detailDescriptionLabel: UILabel!
var detailItem: AnyObject? {
didSet {
// Update the view.
self.configureView()
}
}
func configureView() {
// Update the user interface for the detail item.
if let detail = self.detailItem {
if let label = self.detailDescriptionLabel {
//label.text = detail.value(forKey: "timeStamp")!.description
label.text = (detail.value(forKey: "timeStamp")! as AnyObject).description
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.configureView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Data model is the original from template, as well as storyboard. Both there are not modified.
You can use the new API and get the benefits out of it by doing something like this:
var coreDataManager: CoreDataManagerProtocol!
#if available(iOS10,*)
{
coreDataManager = CoreDataManagerNewStack()
} else
{
coreDataManager = CoreDataManagerOldStack()
}
and then you implement the new CoreData with the new stack into a class:
class CoreDataManagerNewStack: CoreDataManagerProtocol {
var container: NSPersistentContainer
// etc
}
and the old one with the code paster above with the whole code to generate the stack
class CoreDataManagerOldStack: CoreDataManagerProtocol {
lazy var managedObjectContext: NSManagedObjectContext = {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
// etc
}

How do you load attribute data from the entity into a single (1) UIViewController?

I'm new to coding with Swift, and I've been using the tutorial from Udemy by Aaron Caines, which has been great. The tutorial he did was using a UITableViewController.
I have an app that uses a single UIViewController (ONLY ONE VIEW and it's not a UITableViewController). I've already loaded CoreData into the build phases. I've been able to verify that the data is saved in the attributes, but for some reason, I can't load the data back into the two text boxes and one image view that I have in the view controller.
I've placed a couple of questions as comments within the code.
It should be as easy as setting up the variables:
#IBOutlet var textName: UITextField!
#IBOutlet var descriptionName: UITextField!
#IBOutlet var imageView: UIImageView!
calling the entity and getting the persistent container ready to load and receive data:
let pc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
fetching the data
var frc : NSFetchedResultsController = NSFetchedResultsController<NSFetchRequestResult>()
func fetchRequest() -> NSFetchRequest<NSFetchRequestResult> {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "NamedEntity")
// DO I NEED A SORTER IF I'M NOT USING A TABLEVIEW?
//let sorter = NSSortDescriptor(key: "accounttext", ascending: false)
//fetchRequest.sortDescriptors = [sorter]
return fetchRequest
}
func getFRC() -> NSFetchedResultsController<NSFetchRequestResult> {
frc = NSFetchedResultsController(fetchRequest: fetchRequest(), managedObjectContext: pc, sectionNameKeyPath: nil, cacheName: nil)
//OCCASIONALLY THERE'S AN ISSUE WITH THE sectionNameKeyPath.
//THE ERROR INVOLVES TRYING TO "UNWRAP A NIL VALUE".
//IS THERE ANOTHER VALUE I SHOULD BE CONSIDERING?
return frc
}
fetching the data whenever the view loads or appears:
override func viewDidLoad() {
super.viewDidLoad()
frc = getFRC()
frc.delegate = self
do {
try frc.performFetch()
}
catch {
print(error)
return
}
// WHAT DO I USE HERE IF I'M NOT USING A TABLEVIEW?
self.tableView.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
frc = getFRC()
frc.delegate = self
do {
try frc.performFetch()
}
catch {
print(error)
return
}
// WHAT DO I USE HERE IF I'M NOT USING A TABLEVIEW?
self.tableView.reloadData()
}
and then loading it into the appropriate boxes:
//THIS IS WHERE THINGS GET STUCK
// HOW DO I CALL THE ATTRIBUTES OF MY ENTITY AND UPDATE MY VARIABLES IF I'M NOT USING A TABLEVIEW?
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! cellAccountTableViewCell
let item = frc.object(at: indexPath) as! Entity
cell.nameText.text = item.accounttext
cell.descriptionText.text = item.amounttext
cell.imageView.image = UIImage(data: (item.image)! as Data)
return cell
}
I only have 2 text boxes and 1 image view. I've spent the last three days scouring dozens of useless forum topics and countless youtube videos for this answer, but it seems that everyone gives a tutorial on using a table view controller.
The most useful thing I've found was a video by Electronic Armory. This helped me understand the structure of the Entity(ies), attrubutes, and the persistentContainer. It also deals with the relational aspect the database.
https://www.youtube.com/watch?v=da6W7wDh0Dw
Can I use core data on ONE (1) single UIViewController, and if so, how do I call the data and load it into the appropriate fields? Let me know if there's any more info needed.
I'm really trying to understand the Core Data process. What am I missing, or what am I not understanding about the loading process? Any help would be appreciated!
Thanks,
Luke
I have an app that uses a single UIViewController (ONLY ONE VIEW and it's not a UITableViewController).
I only have 2 text boxes and 1 image view.
Assuming there's only one entity returned from the fetch (otherwise how are you going to show them all?), probably something like this:
// WHAT DO I USE HERE IF I'M NOT USING A TABLEVIEW?
//self.tableView.reloadData()
if let fetchResult = frc.fetchedObjects{
if let item = fetchResult.first as? Entity{
textName.text = item.accounttext
descriptionName.text = item.amounttext
imageView.image = UIImage(data: (item.image)! as Data)
}
}

How does encoding and decoding functions work in Swift 3 using NSCoding?

Good afternoon!
Can anyone help me understand how saving data using NSCoding works in Swift? I am a beginner in this language and I am currently watching some tutorials on how to work with table views(create cells, saving data, etc).
The code below creates a UITableView where I can just add the first and last name of an employee. However, I cannot understand how these encoding and decoding functions work, because it has been assigned just one key to the first name and one key to the last name(that's what I understood). I mean that, because it is an array of employees, and is these functions intelligent enough to give the same keys to the first and last names of all the employees and then to retrieve the data?
ViewController class:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var data = [Employee]()
#IBOutlet weak var myTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
loadData()
}
var filePath: String {
let manager = FileManager.default
let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first
return url!.appendingPathComponent("Data").path
}
private func loadData(){ //Decode
if let ourData = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? [Employee] {
data = ourData
}
}
private func saveData(employee: Employee){ //Encode
self.data.append(employee)
NSKeyedArchiver.archiveRootObject(data, toFile: filePath)
}
func numberOfSections(...)
func tableView(...)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//Could be UITableViewCell(), but for a better performance we use this reusable form below:
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
//"indexPath" will return the information based on the number of rows we have. The number of rows in this case is "data.count"
cell.textLabel?.text = data[indexPath.row].Name
cell.detailTextLabel?.text = data[indexPath.row].LastName
return cell
}
#IBAction func addEmployee(_ sender: AnyObject) {
let alert = UIAlertController(title: "Add New Employee", message: "Enter Employee's name", preferredStyle: .alert)
let saveButon = UIAlertAction(title: "Save", style: .default){
(alertAction: UIAlertAction) in
let employeeName = alert.textFields?[0].text!
let employeeLastName = alert.textFields?[1].text!
let newEmployee = Employee(name: employeeName!, lastName: employeeLastName!)
self.saveData(employee: newEmployee)
self.myTableView.reloadData()
}
let cancelButton = UIAlertAction(title: "Cancel", style: .default, handler: nil)
alert.addTextField(configurationHandler: nil)
alert.addTextField(configurationHandler: nil)
alert.addAction(saveButon)
alert.addAction(cancelButton)
self.present(alert, animated: true, completion: nil)
}
}
Employee class image:
I hope you understood my questions, otherwise let me know. Thank you very much!
The keys are what the coder uses to serialize and deserialize the properties of your class. You can give them any string value. You could call the firstName property key "firstName" if you like instead of "name" but they have to match the value of when you code and decode the object. If they do not you will not get the values you expect. The class has to match as well to the class being decoded and encoded so beware of changing this in the future or you will have a crash. The data will absolutely persist between launches. The data is stored in the app file directory. Here is a link to a project that uses the exact code above. You can see for yourself that it will persist between launches.
https://www.dropbox.com/s/zuxd1kt3brkxtgi/Archiving.zip?dl=0

Unable to share data using Shared App Groups for Today Extension

I am trying to create a today extension that displays data from the parent app by using a shared app group container, and then adding the persistent store to a context.
Add Today Extension Target
Turn on app groups for parent app and extension and select same group
Add Today Extension as Target Membership for Data model and entities
Add Persistent store to context
Fetch Objects
I get no errors but the extension does not seem to be fetching any results. Does anybody have any suggestions where I may be going wrong ?
Heres what I am doing in the extension TodayViewController
class TodayViewController: UIViewController, NCWidgetProviding {
var context: NSManagedObjectContext!
#IBOutlet weak var table: UITableView!
var objectsArray = [Objects]()
override func viewDidLoad() {
super.viewDidLoad()
let fileManager = NSFileManager.defaultManager()
var containerPath = fileManager.containerURLForSecurityApplicationGroupIdentifier("group.com.Company.AppName")
containerPath = containerPath?.URLByAppendingPathComponent("SingleViewCoreData.sqlite")
let modelURL = NSBundle.mainBundle().URLForResource("AppName", withExtension: "momd")
let model = NSManagedObjectModel(contentsOfURL: modelURL!)
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model!)
do {
try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: containerPath, options: nil)
} catch {
print("yellow")
}
context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
context.persistentStoreCoordinator = coordinator
let moc = context
let request = NSFetchRequest(entityName: "Objects")
request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)]
do {
try
self.objectsArray = moc.executeFetchRequest(request) as! [Objects]
print ("objects count \(objectsArray.count)")
} catch {
// failure
print("Fetch failed")
}
self.table.reloadData()
}
// MARK: - Table view data source
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
//return sectionsArray.count
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.objectsArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = table.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell!
cell.textLabel!.textColor = UIColor.whiteColor()
cell.textLabel!.text = self.objectsArray[indexPath.row].title
return cell
}
func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)) {
// Perform any setup necessary in order to update the view.
// If an error is encountered, use NCUpdateResult.Failed
// If there's no update required, use NCUpdateResult.NoData
// If there's an update, use NCUpdateResult.NewData
completionHandler(NCUpdateResult.NewData)
}
}
In Apple's App Extension Programming Guide --Sharing Data with Your Containing App
Even though an app extension bundle is nested within its containing app’s bundle, the running app extension and containing app have no direct access to each other’s containers.
So they wouldn't be able to share data directly,even if you
Add Today Extension as Target Membership for Data model and entities
Instead,they communicated with each other by shared container(App groups) indirectly,which you had already setup in step 2.
So the solution is :
In your Contraning App,create the sql data model in your shared container ,insert data in this database.Adjust your code in todayExtension ,and fetch data.
Or Using NSUserDefaults to share data instead.
like this:
// Create and share access to an NSUserDefaults object
NSUserDefaults *mySharedDefaults = [[NSUserDefaults alloc] initWithSuiteName: #"com.example.domain.MyShareExtension"];
// Use the shared user defaults object to update the user's account
[mySharedDefaults setObject:theAccountName forKey:#"lastAccountName"];

Resources