1

I have a textView which is the whole screen of the app. The users input new text, change the properties etc. I want the app save textview content with its properties for next times. I came up with userDefaults. I tried much but nothing happens. I couldn't find a solution to this. I tried this solution from stackoverflow https://stackoverflow.com/a/36940864/12698480 but no change. Again empty screen comes on the textview. My code as an extension to Userdefaults is as below:

//Save text
func setNSMutableText(string: NSMutableAttributedString?, forKey key: String){
        var stringData: NSData?
        if let string = string {
            do {
                stringData = try NSKeyedArchiver.archivedData(withRootObject: string, requiringSecureCoding: false) as NSData?
                set(stringData, forKey: key)
                print("archived NSMutableData")
            } catch let err {
                print("error archiving string data", err)
            }
        }
    }

//LOAD text
    func getNSMutableForKey(key: String) -> NSMutableAttributedString? {
        var string: NSMutableAttributedString?
        if let stringData = data(forKey: "screentextKey") {
            do {
                string = try NSKeyedUnarchiver.unarchivedObject(ofClass: NSMutableAttributedString.self, from: stringData)
                print("UNARchived string data!!!")
            } catch let err {
                print("error extracting string data", err)
            }
        }
        return string
    }

Usage:

//For saving
UserDefaults.standard.setNSMutableText(string: NSMutableAttributedString(attributedString: myTextView.attributedText), forKey: "screentextKey")

//For loading
override func viewDidLoad() {
   super.viewDidLoad()

   let oldstring = (UserDefaults.standard.getNSMutableForKey(key: "screentextKey"))!
   myTextView.attributedText = oldstring
}

What is the problem? I saw that someone's suggestion about using NSAttributedString at the place of NSMutableAttributedString. I tried that way also, but no change.

Anthon Santhez
  • 402
  • 1
  • 7
  • 13
  • 1
    What is printed in console? When do you call setNSMutableText? – Jordan Dec 10 '20 at 08:54
  • Not the problem, but: You pass in the key to "getNSMutableForKey", but you don't use it. – Jordan Dec 10 '20 at 08:57
  • @Jordan This is a calculator app. I call setNSMutableText at the calculate "=" symbol. I cannot see anything in the console since connection gets lost after destroying the app. – Anthon Santhez Dec 10 '20 at 09:00
  • @Jordan I used the key with getNSMutableForKey at the viewDidLoad section, it can be seen at the post – Anthon Santhez Dec 10 '20 at 09:05
  • " I cannot see anything in the console since connection gets lost after destroying the app." but when you launch/run is, you see it, no? You should get code from `getNSMutableForKey`. – Larme Dec 10 '20 at 09:10
  • @Jordan Yes when I launch again from XCode after destroying the app, console says "UNARchived string data!!!" But the text view is empty :( Meanwhile, when I click the button, it says "archived NSMutableData" so no problem at the saving, but the problem is at the loading section – Anthon Santhez Dec 10 '20 at 09:12
  • 1
    When you do `let oldstring = ...` could you print its value? Is the myTextView text/attributedText override afterward? – Larme Dec 10 '20 at 09:47
  • @Larme You are genius man! Your question gave me the idea. Now I managed it, the problem was that I forgot the existence of clearing the screen(textview) line inside viewDidLoad section. When I removed that line, everything worked fine. Thank you very much! – Anthon Santhez Dec 10 '20 at 10:40

1 Answers1

1

In order to make it work the way you want, you don't have to use NSKeyedArchiver.archivedData. Instead it is much better to use CoreData to save the data in device memory(Because if I got the idea it is not the big amount of data), Structure it using XCDataModel and then, fetch it using NSFetchController.

in XCDataModel you have to create ENTITY and set attributes for it(String, Bool, Double etc.)

After that you will have to set NSFetchedResultsController in order to manage the process.

Here is the way I would set it :

  1. Import CoreData

  2. set persistent container(Container for your data):

    static let persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "RebelFitness") container.loadPersistentStores { (_, error) in if let error = error as NSError? { fatalError("Unresolved error (error), (error.userInfo)") } } return container }()

  3. set NSFetchedResultsController:

    lazy var fetchedResultsController: NSFetchedResultsController = {

         let fetchRequest: NSFetchRequest< YourEntityName > = YourEntityName.fetchRequest()
         fetchRequest.sortDescriptors = [NSSortDescriptor(key: "distance", ascending: true), NSSortDescriptor(key: "duration", ascending: true)] //Here you use descriptor to sort the data in my example distance comes in array first and then any other elements
         let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: CoreDataStack.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)
         fetchedResultsController.delegate = self as? NSFetchedResultsControllerDelegate
    
         return fetchedResultsController
    
     }()
    
  4. Function used to fetch the saved data:

static var context: NSManagedObjectContext { return persistentContainer.viewContext }

In this function we are fetching data to new array which will pass it to your array which supplies the UIKit elements with data

func loadIsCurrent() -> [YourEntityName] {
        
        var _data : [YourEntityName] = []
        let context = persistentContainer.viewContext
        let fetchRequest: NSFetchRequest< YourEntityName > = YourEntityName.fetchRequest()
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "distance", ascending: false), NSSortDescriptor(key: "duration", ascending: true)]
        
        do {
            let data = try context.fetch(fetchRequest)
            if(sessions.count > 0) {
                _data = data
            }
        } catch {
            print("Something happened while trying to retrieve tasks...")
        }
        return _data
    }
  1. saving the data to coreData:

func saveContext () { let context = persistentContainer.viewContext

guard context.hasChanges else {
  return
}
    
do {
  try context.save()
    try fetchedResultsController.performFetch()
} catch {
  let nserror = error as NSError
  fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
    }

}

  • I think Userdefaults is much more inexpensive and easy to use. But if there isn't a way for NSMutableAttributedStrings, then seems that I should learn the coreData. I didn't use coredata before, so no experience. I will now try to implement this, if succeeds I will accept the answer. – Anthon Santhez Dec 10 '20 at 10:26
  • At the beginning I also tried archiving files but unfortunately didn't find any way to accomplish it by doing so. My researches shown that coreData is the only way, of course if you don't want to use any other online data bases such as FireBase:) – Aisultan Askarov Dec 10 '20 at 12:53
  • I succeeded it. The problem was that I forgot the existence of clearing the screen(textview) codeline inside viewDidLoad section. When I removed that line, everything worked fine. Anyway, thank you very much for your contribution. Currently no need for CoreData :) – Anthon Santhez Dec 10 '20 at 13:04
  • Happy that you succeeded:) By the way don't forget to check if data stays in plays after you close the application from task manager on the device(I had a lot of troubles with it in past:( ) – Aisultan Askarov Dec 10 '20 at 13:06
  • thanks for the advice my brother. I tried that too, everything works fine :) – Anthon Santhez Dec 10 '20 at 17:43