Persisting application data - ios

I'm fairly new to programming but at the moment. I can view my calculations in my stack on the second screen. Once the application project resets the stack is clear. My question is how I can keep the stack in the second view once the application has exited.
class ViewController: UIViewController {
var name = Array<String>()
#IBOutlet weak var labelDisplay: UILabel!
var calcEngine : CalculatorEngine?
override func prepareForSegue(segue: UIStoryboardSegue,
sender: AnyObject?) {
if segue.identifier == "pushToNextView"
{
let secondVC: SecondVCViewController = segue.destinationViewController as! SecondVCViewController
secondVC.data = self.name
}
}
second view
class SecondVCViewController: UIViewController {
#IBOutlet weak var textData: UITextView!
var data = Array <String>()
override func viewDidLoad() {
super.viewDidLoad()
for(var i = 0; i < self.data.endIndex; i++ ){
self.textData.text = self.textData.text! + self.data[i] + "\n"
}
// Do any additional setup after loading the view.
}

There are many tools available to you to persist data from an app. Choosing an appropriate one requires some understanding of your needs, or at least making a guess. Here's where I would start but keep an eye out in case my assumptions don't match your needs.
What type of data do you want to persist?
Presumably you want to store the array of Strings seen in var name = Array<String>() but maybe you have some other data type in mind.
When do you need to read/write it (do you need to worry about multiple systems trying to write at the same time, is loading the data going to be very expensive because it is very large, do you only need to load a small piece at a time, ...)?
Sounds like you're fine reading/writing all the data at once and only on app launch/termination. There could be a reasonable maximum size to this calculation history to total storage size is probably fairly small.
How long does it need to be persisted?
Calculation histories are usually nice to have but not a catastrophic loss if they go missing. It would be nice to keep this data around but the app will work without it. Depending on use it may or may not cost the user time and frustration if the data is deleted unexpectedly.
Who needs access to it (is it also show on a web site, should it sync to all of a user's devices, ...)?
It's probably enough to keep this history just on the local device.
So what should we do?
For a small amount of data loaded all at once and only used locally I would write this data to a file.
Since you're just working with an array of strings we can use a plist (https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/PropertyLists/AboutPropertyLists/AboutPropertyLists.html)
NSArray (note: not a Swift array) has arrayWithContentsOfURL: and writeToURL:atomically: which can be a convenient way to read and write plist compatible data.
Using a file means we need to decide where to store this file. Take a look at Where You Should Put Your App’s Files. In this case it seems reasonable to write this data to either Documents or Library/Caches depending on how you plan to use it.
With all that covered we could save this stack something like:
let array = names as NSArray
guard let cachesURL = NSFileManager.defaultManager().URLsForDirectory(.CachesDirectory, inDomains: .UserDomainMask).first else {
print("Unable to find Caches directory, cannot save file.")
return
}
let fileURL = cachesURL.URLByAppendingPathComponent("history.plist")
array.writeToURL(fileURL, atomically: false)
Reading the file is similar. You'll need the same URL so decide which component is responsible for deciding where to save/load this data and let it supply the URL to use. You'll also need to check if any saved history exists or not (note that arrayWithContentsOfURL: can return nil).
At some point you might find it useful to model this data as a more specific data type, perhaps defining your own struct to capture these operations. A custom type can't be automatically written to or read from a file so you'll need to do a little more work. Apple has a nice example of how you might use NSCoding to do that in their tutorials: https://developer.apple.com/library/ios/referencelibrary/GettingStarted/DevelopiOSAppsSwift/Lesson10.html

Preserving and Restoring State
Feature you are looking for is called State Restoration. From the docs:
State preservation records the configuration of your app before it is
suspended so that the configuration can be restored on a subsequent
app launch.
How to tackle it:
Tag your view controllers for preservation. Assign restoration identifiers.
Restore view controllers at launch time. Encode and decode their state.
Topic is very broad but the docs explain it very well.

What you're trying to do is keep persistent data between launches of your application, right?
For that you should use core data, there are many useful recourses on the web to help you with that, heres a few helpful ones.
https://developer.apple.com/library/watchos/documentation/Cocoa/Conceptual/CoreData/index.html
https://www.raywenderlich.com/934/core-data-tutorial-for-ios-getting-started

Related

Should I use NSUserDefault, dictionaries, core data - or something else?

I'm having some issues with the app, that I'm making, which I thought would be a lot easier to explain with some photos, so ... :
Ofcourse the "Create New Person-button" in nr. 1 leads you to number two.
Now, I'm having issues figuring out how to save this data about the person in the "People Diary". The goal is, that when you enter a person's name, add a photo (an enable-camera feature, I will struggle with at a later time...) and add an answer to the question - then you only need to press "Save this person", and then you will be redirected to the AllPersonsInYourDiaryViewController, where there is now a new tableViewCell with this new person's name (maybe with a subtitle containing the answer and the photo shown in miniature in the cell too).
(Naturally you can then enter this cell with the data about the person too - but that comes next.)
So far in the app, I have used NSUserDefault, when allowing the user to create this specifik Diary by the Name "Antons Diary" with the specifik question and so on. But now it came to my attention, that maybe it is smarter to use something else? I tried with dictionaries, but couldn't get this to work properly.
So...: Before I spend hours and hours playing around with one of these ways, will someone smarter than me, tell me what the best approach would be?
If I can give my two cents, the first thing you have to do is to “design” how to represent a person programmatically. You can create a struct or class to do so, even though a struct is more suitable:
struct Person {
var name: String?
var answer: String?
var photo: String?
}
Then you can decide how to save the data of such an object persistently. If you want to use a database, then I would recommend using SQLite with FMDB library. It’s really easy and fast to learn how to use it, and it's also quite handy. I've used it big projects and it works smoothly. I find CoreData too complicated and an overkill based on what you need.
If you don’t want to use a database, your only other way is to save to files, but still, you’ve got options here too. If you encode (see Codable protocol in Swift), you can use NSKeyedArchiver to convert to Data object and write then to disk. If you like using dictionaries, and since the properties you’re going to have for a person are not going to be too many, you could create a dictionary by assigning the properties and their values, and then convert and save as JSON data, or even Plist files. Without any intension to do promotion here, but just to provide some additional help, if you want take a look to a library that I’ve written and that can do all these automatically for you. It’s a protocol that you have to adopt, and then you can instantly convert your struct to a dictionary, JSON or plist and save to files.
No matter which way you’re going to select, save the images as single files to documents directory, and keep their file names only stored to database/file. Based on them, you can build the path to each image (or the URL) easily when needed. Warning: Do not save the full path to the documents directory, especially if you’re testing on Simulator; paths are changing on each build. Save the file name only.
Additionally, if you’re going to use a struct like the one shown above, you could implement small but super convenient functions that will be responsible for saving, loading, or updating your data to the solution (database/file) you’ll eventually select. That way, you’ll have related stuff gathered in one place, and easily accessible (i.e., person.save()).
struct Person {
var name: String?
var answer: String?
var photo: String?
func save() {
…
}
func load() {
…
}
// More functions…
}
Lastly, avoid using UserDefaults, or at least keep just a few non-critical data there. UserDefaults are not meant to keep all data produced by your app. Most importantly, do not use it for saving sensitive data, especially passwords or other stuff like that.
I hope the above will help you make your mind.
I can give you the logic behind coreData and NSUserDefaults, but you will decide which one should be used.
CoreData is usually used as a database. you can create entities and attributes for every entity. Moreover, you can create relations between these entities.
When extracting data from coreData, you can arrange this data using NSSortDescriptor or select a specific record using NSPredicate.
So as you can see CoreData is a database.
While NSUserDefaults is usually used to save a password, username, userID... and such issues that you will regularly use in the app. NSUserDefaults gives you a direct access to the saved variables at any time. However, CoreData will take more time and lines of code to access the entity and make the query.
Now, check which method suits your case more.

Swift - Variable scope with loading external data

I am using the Mapbox framework. I have used their Studio (web app) to create some map shapes (polygons and coordinates) that I have downloaded as a GeoJSON file. This file is bundled with my iOS app. All provided examples are very small and have a simple structure, like within viewDidLoad will all the remaining code.
I am trying to design the app so that the data is loaded once, and then this variable will be accessible to be able to add and / or remove some of these mapping items as needed.
Should I use a global variable? If so, where should I declare the data variable, so it can be accessed anywhere? Or is this bad practice, and I should load the data variable once somewhere with less scope access and pass the object itself around within appropriate function calls? Does this not also get confusing? Where would the best location be for the initial data load, viewDidLoad()?
do {
let url = URL(fileURLWithPath: Bundle.main.path(forResource: "features", ofType: "geojson")!)
let data = try Data(contentsOf: url)
} catch {
print(error)
}
Then I will have other functions that can then filter this data set, and only add or remove specific map objects.
func loadSomeData(forGroup name: String, withData data: Data) {
let shapeCollection = try MGLShape(data: data, encoding: String.Encoding.utf8.rawValue) as! MGLShapeCollectionFeature
for shape in shapeCollection.shapes {
if shape.attribute(forKey: "group") as! String == name {
if let point = shape as? MGLPointAnnotation {
// ADD ITEM TO MAP
} else if let polygon = shape as? MGLPolygon {
// ADD ITEM TO MAP
}
}
}
}
var group = "group1"
loadSomeData(forGroup: group)
So this would filter the same original data source for all objects with a property of "group1" and only load those (it will be preceeded by removing existing objects, and may do many other things - I just need to grasp the basics...)
Ultimately, I would prefer to parse the GeoJSON data file, and create custom objects for grouped items. But the file's structure is totally random and means the object class' properties would need to be entirely optionals, but some are lazy loaded / computed properties which don't work well with optionals, from my early testing...
Sounds like you have a couple of issues: Loading data from your bundle, and making it globally accessible.
If the data will never change then simply reading it from your bundle should be fine. If it might change during the life of your app, you might want to write code that's run at launch that will check for the file in documents at launch, and copy it from the bundle to the documents directory if it's not found, then open it from documents.
Then you'd need to query your server to see if your data is current (using a version number or update timestamp.) If it's changed, you'd download the new data, save the changes to documents, and also update the version/timestamp.
As to making your data available app-wide, this might be a good use case for a data container singleton. The singleton would have methods to query your map data, and it would be responsible for the loading/updating logic I described above.
Alternately you might decide that the data would map well to Core Data.
As to how to design your data model, that's a separate conversation and you'd need to provide more information.

iOS Swift 3 - How to save data that can be pulled to any View Controller

I'm making a game that has skills which each have their own level and experience depending on how much they player has trained them. It also has things like money and items stored in a bank.
Where should I save these items so that you can access it from any view controller and so that it will save when you close and open the game?
I've decided to try and use the UserDefualts but I'm not sure what I'm doing wrong.
Could someone explain if I wanted to have a variable called coins and have a label that displayed these coins starting from 0 and every time a button is clicked the coins go up by 1. Then also be able to close the game or witch views and have the coins stay the same as before it was closed?
Firstly you should define if the game data is sensitive.
If it is not sensitive, try to use NSUserDefaults, it is a fast way to provide primitive storage for the data that you have described.
If it is and data should be protected, try to use more sophisticated ways like CoreData and Realm. They have a bunch of security wrappers that will not give so easy way to manage saved data.
But you also could use cool features such as iCloud, this option will give you the possibility to store and sync game data between platforms and be quite secure.
You can create a singleton and store your data there.
class GameState {
static let shared = GameState()
var items: [Items]
var balance: Int
var level: Int
var xp: Double
func save() {
// Save to CoreData
}
}
To access it from any other class:
GameState.shared.xp = 30
GameState.shared.save()
You would of course store the data in a database, but the singleton gives you a convenient access.

How do I save a var when my iOS app closes? [duplicate]

This question already has answers here:
How to save local data in a Swift app?
(12 answers)
Closed 6 years ago.
In total I have 2 viewcontrolers. The App is the same like this guy made in a tutorial. https://www.youtube.com/watch?v=LrCqXmHenJY&t=40s
To make a start, I made a var:
import UIKit
var list = ["Task 1", "Task 2", "Task 3", "Task 4"]
class FirstViewController: UIViewController , UITableViewDelegate, UITableViewDataSource{
My current problem is, that when I close the app no ToDo's which I created were saved.
What do I need to do to save my var list so that my ToDo-List isn't empty when I open it for the next time?
You can use UserDefaults to save/load application state or configurations. For complex requirements use CoreData
Writing/Saving before app termination
let defaults = UserDefaults.standard
defaults.set(true, forKey: "Enabled")
Reading/Loading on app launch
let defaults = UserDefaults.standard
let enabled = defaults.bool(forKey: "Enabled")
Read the related Apple docs here.
If you want an easy way to store a list of strings, you can use UserDefaults. Another way to do this is to use Core Data which is more difficult to use. It is usually used to store more complex data structures than an array of strings.
To save the string array to UserDefaults, do this:
UserDefaults.standard.set(list, "myStringArray")
You can replace "myStringArray" with any key you want.
To retrieve it, do this:
list = UserDefaults.standard.stringArray(forKey: "myStringArray")
I suggest you save the array in this method in App Delegate:
func applicationDidEnterBackground(_ application: UIApplication) {
}
For more info on User Defaults, see https://developer.apple.com/reference/foundation/userdefaults
You have very simple needs. Look at NSUserDefaults (now called UserDefaults in Swift 3.)
Save your data to UserDefaults when it changes, and read it back when you display your view controller.
As Eric says in his comments, make list an instance variable of your view controller.
EDIT:
In the comments below, rmaddy pointed out that the app will be saving a to-do list. That will likely grow in complexity from an array of a handful of items to a larger array or potentially a more complex structure.
Given that, you might want to look at saving your data to a plist file in the documents directory (quite simple) or using NSCoding if you have custom objects other than the small list of "property list" types that can be saved to a plist.
Both of those solutions read and write the data all at once to a file. That might be ok for a to-do list app.
If you need to change parts of a larger data-set and leave the rest unchanged then it's time to consider a database like Core Data.

Creating a PDF (Or Other viewable format, such as .docx) from CoreData using swift

Description
Hello! So i am saving several strings of user inputed data into CoreData using a handful of NSManagedObject subclasses. Since the application is actually a machine inspection, it is necessary to print/email the customer entered information out in a somewhat structured format. Below you will find pictures/examples of how i am saving the data. Please be kind to me, for it is my birthday <3
Code examples
Below is an example of how i am saving the information. In the example, the CoreData entity is "CrawlerThree" and the data is crawlerDistance.
#IBAction func save(_ sender: AnyObject) {
let appDel:AppDelegate = (UIApplication.shared().delegate as! AppDelegate)
let context:NSManagedObjectContext = appDel.managedObjectContext
let entity1 = NSEntityDescription.insertNewObject(forEntityName: "CrawlerThree", into:context) as NSManagedObject as! CrawlerThree
entity1.crawlerDistance = distance.text
}
The rest of the saves are almost identical to this, with varying entities and data, all of which are strings. I know this is a rather vague post, but i really do need to know what i need to use to format this information. It is worth noting that i already know how to actually retrieve the string's from CoreData, I am simply ignorant on how to actually process them to some other form.
The Simplest solution will be the best solution for my problem! Thanks in advance!
I would divide the problem into two.
Output from Core Data to screen
Export as PDF
It sounds as if you know how to get your strings from Core Data already, so you just have to display them in a suitable way.
Fortunately whatever you use on screen: UIView, UITableView etc. can be saved as a PDF using e.g. the (Swift) answer to How to Convert UIView to PDF within iOS?

Resources