Saving data in UITextView - ios

I'm writing notes app for iOS and I want all data which user enter in notes will be automatically saved when user typing automatically. I'm using Core Data and now I save data on viewWillDisappear, but I want the data also be saved if user terminate the app or the app will be automatically terminated in the background.
I use this code:
import UIKit
import CoreData
class AddEditNotes: UIViewController, UITextViewDelegate {
#IBOutlet weak var textView: UITextView!
var note: Note!
var notebook: Notebook?
var userIsEditing = true
var context: NSManagedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
context = appDelegate.persistentContainer.viewContext
if (userIsEditing == true) {
textView.text = note.text!
title = "Edit Note"
}
else {
textView.text = ""
}
}
override func viewWillDisappear(_ animated: Bool) {
if (userIsEditing == true) {
note.text = textView.text!
}
else {
self.note = Note(context: context)
note.setValue(Date(), forKey: "dateAdded")
note.text = textView.text!
note.notebook = self.notebook
}
do {
try context.save()
print("Note Saved!")
}
catch {
print("Error saving note in Edit Note screen")
}
}
}
I understand what I can use applicationWillTerminate for this, but how I can pass there the data user entered? This functionality is in default notes app from Apple. But how it can be released?

There are two subtasks to saving the data: updating the Core Data entity with the contents of the text view and saving the Core Data context.
To update the contents of the Core Data entity, add a function to the AddEditNotes class that saves the text view contents.
func saveTextViewContents() {
note.text = textView.text
// Add any other code you need to store the note.
}
Call this function either when the text view ends editing or the text changes. If you call this function when the text changes, the Core Data entity will always be up to date. You won't have to pass the data to the app delegate because the app delegate has the Core Data managed object context.
To save the Core Data context, add a second function to the AddEditNotes class that saves the context.
func save() {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.saveContext()
}
}
This function assumes you selected the Use Core Data checkbox when you created the project. If you did, the app delegate has a saveContext function that performs the Core Data save.
You can now replace the code you wrote in viewWillDisappear with the calls to the two functions to save the text view contents and save the context.
The last code to write is to go to your app delegate file and add the following line of code to the applicationDidEnterBackground and applicationWillTerminate functions:
self.saveContext()
By adding this code your data will save when someone quits your app.

Related

How do refresh my UITableView after reading data from FirebaseFirestore with a SnapShotListener?

UPDATE at the bottom.
I have followed the UIKit section of this Apple iOS Dev Tutorial, up to and including the Saving New Reminders section. The tutorials provide full code for download at the beginning of each section.
But, I want to get FirebaseFirestore involved. I have some other Firestore projects that work, but I always thought that I was doing something not quite right, so I'm always looking for better examples to learn from.
This is how I found Peter Friese's 3-part YT series, "Build a To-Do list with Swift UI and Firebase". While I'm not using SwiftUI, I figured that the Firestore code should probably work with just a few changes, as he creates a Repository whose sole function is to interface between app and Firestore. No UI involved. So, following his example, I added a ReminderRepository.
It doesn't work, but I'm so close. The UITableView looks empty but I know that the records are being loaded.
Stepping through in the debugger, I see that the first time the numberOfRowsInSection is called, the data hasn't been loaded from the Firestore, so it returns 0. But, eventually the code does load the data. I can see each Reminder as it's being mapped and at the end, all documents are loaded into the reminderRepository.reminders property.
But I can't figure out how to get the loadData() to make the table reload later.
ReminderRepository.swift
class ReminderRepository {
let remindersCollection = Firestore.firestore()
.collection("reminders").order(by: "date")
var reminders = [Reminder]()
init() {
loadData()
}
func loadData() {
print ("loadData")
remindersCollection.addSnapshotListener { (querySnapshot, error) in
if let querySnapshot = querySnapshot {
self.reminders = querySnapshot.documents.compactMap { document in
do {
let reminder = try document.data(as: Reminder.self)
print ("loadData: ", reminder?.title ?? "Unknown")
return reminder
} catch {
print (error)
}
return nil
}
}
print ("loadData: ", self.reminders.count)
}
}
}
The only difference from the Apple code is that in the ListDataSource.swift file, I added:
var remindersRepository: ReminderRepository
override init() {
remindersRepository = ReminderRepository()
}
and all reminders references in that file have been changed to
remindersRepository.reminders.
Do I need to provide a callback for the init()? How? I'm still a little iffy on the matter.
UPDATE: Not a full credit solution, but getting closer.
I added two lines to ReminderListViewController.viewDidLoad() as well as the referenced function:
refreshControl = UIRefreshControl()
refreshControl?.addTarget(self, action: #selector(refreshTournaments(_:)), for: .valueChanged)
#objc
private func refreshTournaments(_ sender: Any) {
tableView.reloadData()
refreshControl?.endRefreshing()
}
Now, when staring at the initial blank table, I pull down from the top and it refreshes. Now, how can I make it do that automatically?
Firstly create some ReminderRepositoryDelegate protocol, that will handle communication between you Controller part (in your case ReminderListDataSource ) and your model part (in your case ReminderRepository ). Then load data by delegating controller after reminder is set. here are some steps:
creating delegate protocol.
protocol ReminderRepositoryDelegate: AnyObject {
func reloadYourData()
}
Conform ReminderListDataSource to delegate protocol:
class ReminderListDataSource: UITableViewDataSource, ReminderRepositoryDelegate {
func reloadYourData() {
self.tableView.reloadData()
}
}
Add delegate weak variable to ReminderRepository that will weakly hold your controller.
class ReminderRepository {
let remindersCollection = Firestore.firestore()
.collection("reminders").order(by: "date")
var reminders = [Reminder]()
weak var delegate: ReminderRepositoryDelegate?
init() {
loadData()
}
}
set ReminderListDataSource as a delegate when creating ReminderRepository
override init() {
remindersRepository = ReminderRepository()
remindersRepository.delegate = self
}
load data after reminder is set
func loadData() {
print ("loadData")
remindersCollection.addSnapshotListener { (querySnapshot, error) in
if let querySnapshot = querySnapshot {
self.reminders = querySnapshot.documents.compactMap { document in
do {
let reminder = try document.data(as: Reminder.self)
print ("loadData: ", reminder?.title ?? "Unknown")
delegate?.reloadYourData()
return reminder
} catch {
print (error)
}
return nil
}
}
print ("loadData: ", self.reminders.count)
}
}
Please try changing var reminders = [Reminder]() to
var reminders : [Reminder] = []{
didSet {
self.tableview.reloadData()
}
}

How can I load all the data stored in the array, when the app starts again (Swift)?

I have to implement a function that loads the stored data into the shopping list array, when the app starts, and a function that stores the current contents of my list when the button is pressed. I used UserDefaults class and it works for the second function (when the button is pressed) but not for the first one (when the app starts). If I restart the app and press the button, I see that only the last input was stored. How can I fix the code if I want to store all data from the array?
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var inputEntered: UITextField!
// keyboard gives up the first responder status and goes away if return is pressed
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
inputEntered.resignFirstResponder()
return true
}
var shoppingList: [String] = []
#IBAction func buttonAddToList(_ sender: UIButton) {
if let item = inputEntered.text, item.isEmpty == false { // need to make sure we have something here
shoppingList.append(item) // store it in our data holder
}
inputEntered.text = nil // clean the textfield input
print(shoppingList.last!) // print the last element to avoid duplicates on the console
storeData()
}
// this function stores the current contents of my list when the button is pressed
func storeData () {
let defaults = UserDefaults.standard
defaults.set(inputEntered.text, forKey: "Saved array")
print(defaults)
}
// to call the function storeDate(), when the app restarts
override func viewDidLoad() {
super.viewDidLoad()
inputEntered.delegate = self
// Do any additional setup after loading the view.
storeData()
}
}
You can add a getter and a setter to your array and persist your values to user defaults. This way you don't need to call storeData and or remembering to load the data when initialising your array:
var shoppingList: [String] {
get {
UserDefaults.standard.stringArray(forKey: "shoppingList") ?? []
}
set {
UserDefaults.standard.set(newValue, forKey: "shoppingList")
}
}
You are calling storeData() in viewDidLoad, but inputEntered is empty then, so you are storing blank data.
Also, defaults.set(inputEntered.text, forKey: "Saved array") doesn't append new data onto the key -- it overwrites what is there. So you are not storing the array, you are only storing the last value.
You need to store shoppingList to store the array.
I'm unsure if I have got what you are asking for but have you tried looking into UITextFieldDelegate.
If you add this, it will ensure add the protocols of which I am sure there is a method that can be called when the user finishes editing text field.

Saving text in UITextView Swift 3

I am creating a To-Do App on IOS Platform Swift 3
I am trying to save note in UITextView so when i hit back or terminate application the data is saved.
StoryBoard Have a UITextView and a save button at the navigation bar
How to make user enter his text in UITextView and save it
class Details: UIViewController, UITextViewDelegate{
// MARK: - IB
#IBOutlet weak var noteText: UITextView!
#IBAction func addNote(_ sender: UIButton) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let addNote = Note(context: context)
addNote.details = noteText.text!
//Saving
(UIApplication.shared.delegate as! AppDelegate).saveContext()
}
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var Notes: [Note] = []
func getData() {
do {
Notes = try context.fetch(Note.fetchRequest())
} catch {
print("Fetching Failed")
}
}
override func viewWillAppear(_ animated: Bool) {
getData()
}
override func viewDidLoad() {
super.viewDidLoad()
let MyIcon = UIImageView(image: UIImage(named: "037_Pen"))
self.navigationItem.titleView = MyIcon
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Any idea how to display it ?
Created Entity called Note with Attribute details of type String
Dealing with data in iOS application
If you want to save your data inside application then you need to do something more inside your application for data saving purpose. This way you can save data inside application weather terminate application it will show your saved data and fetch again.
1.) For Short Date save can use UserDefaults
2.) By using SQLite
3.) By Using Coredata
4.) By Using Realm, For more details check Example.
You need to create database to save the text value every time as per your requirement.
You can create the database by using any one of the below :
Core dataGet tutorial from here
SQLite Get tutorial from here
Save your text data by using anyone these and then fetch the data and assign at the UI.

How do I transfer data from one table view cell to another?

I am trying to create a favorites page. How can I let a user click on an image in one table view and then the data from the table view they clicked on is transferred to another tableview in another page?
#IBAction func favoritesSelected(sender: AnyObject)
{
if toggleState == 1
{
sender.setImage(UIImage(named:"Star Filled-32.png"),forState:UIControlState.Normal)
isFav = true
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext
var newFave = NSEntityDescription.insertNewObjectForEntityForName("Favorites", inManagedObjectContext: context) as NSManagedObject
newFave.setValue("" + nameLabel.text!, forKey: "favorite")
do
{
try context.save()
}
catch _
{
print("error")
}
//print("\(newFave)")
print("Object saved")
toggleState = 2
}
From the code above, you can see what happens when a user clicks on the favorites button. The image changes and it uploads the name to the core data.
I'm trying to get it to go to another table view cell class so that when it gets to the favorites page, the names that were favorited will already be there.
I will show what I have in that class but I'm sure it's wrong.
if (result == 2)
{
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext
var request = NSFetchRequest(entityName: "Favorites")
request.returnsObjectsAsFaults = false
do
{
var results:NSArray = try context.executeFetchRequest(request)
if (results.count <= 0)
{
print("Either all object deleted or error")
}
}
catch _
{
print("error")
}
}
else
{
print("no show")
}
Option 1: NSNotification triggers tableView reload:
Register the UITableView tableView with the notification:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "contextDidSave:", name: NSManagedObjectContextDidSaveNotification, object: nil)
func contextDidSave(sender: NSNotification) {
tableView.reloadData()
}
After the user clicks on the star in the first example and the context was saved properly, the contextDidSave callback will be executed and the tableView will load with the latest state in the DB
Option 2: Setup UITableView with NSFetchedResultsController
With this option, once the user clicks on the star and the context saves, iOS will trigger the update to selected cells automatically. See this article for reference:
https://developer.apple.com/library/ios/documentation/CoreData/Reference/NSFetchedResultsController_Class/
In my applications with a favourite function I have done the following:
1) On my model objects have a BOOL property called favourite, with a default value of false.
2) In the table view which lists all objects, when a user taps the favourite button, set the favourite property on the corresponding model object to true and save your managed object context.
3) In the table view in which you wish to display the favourited objects, query core data for all of your model objects with a favourite property that is true and display those results. As mentioned in Christopher Harris' answer, this is trivial if you are a using an NSFetchedResultController.

How to reload UIViewController data from App Delegate

I have some methods that I am calling from the appDelegate to sync changes with persistent storage and iCloud.
The methods and the appDelegate work fine, my app syncs changes fine; however when the app is mergingChanges and persistentStoreDidChange I am trying to refresh the view controller data and change the view controller title to syncing.
I have tried changing the UIViewController title text and it does not change when merging or persistentStoreWillChange, also when using the reloadData() method for the view controller collection the app crashes with an unexpected nil when unwrapping an optional value.
The thing is the project has many tableview & colectionview all within a UITabController so I really need a refresh the data of the view controller in the window not just one specific view. Does anybody know how to refresh the viewcontroller data from the appDelegate ?
func mergeChanges(notification: NSNotification) {
NSLog("mergeChanges notif:\(notification)")
if let moc = managedObjectContext {
moc.performBlock {
moc.mergeChangesFromContextDidSaveNotification(notification)
self.postRefetchDatabaseNotification()
}
}
let vc = CollectionViewController()
let view = self.window?.rootViewController
vc.title = "Syncing"
view?.title = "Syncing"
}
func persistentStoreDidImportUbiquitousContentChanges(notification: NSNotification) {
self.mergeChanges(notification);
}
func storesWillChange(notification: NSNotification) {
NSLog("storesWillChange notif:\(notification)");
if let moc = self.managedObjectContext {
moc.performBlockAndWait {
var error: NSError? = nil;
if moc.hasChanges && !moc.save(&error) {
NSLog("Save error: \(error)");
} else {
// drop any managed objects
}
moc.reset();
}
let vc = CollectionViewController()
vc.title = "Syncing"
// reset UI to be prepared for a totally different
// don't load any new data yet.
}
}
func storesDidChange(notification: NSNotification) {
// here is when you can refresh your UI and
// load new data from the new store
let vc = CollectionViewController()
// vc.collectionView.reloadData()
NSLog("storesDidChange posting notif");
self.postRefetchDatabaseNotification();
}
For above functionality you can use NSNotification Fire that notification to multiple classes when you want to update .

Resources