UIPickerView won't show the data loaded from NSUserDefaults - ios

EDIT:
I tried to set up NSUserDefaults last night but an error keeps occurring:
ViewController3:
save data
#IBAction func addButtonTapped(sender: UIButton) {
var userDefaults:NSUserDefaults = NSUserDefaults.standardUserDefaults()
var itemList:NSMutableArray? = userDefaults.objectForKey("exercisesList") as? NSMutableArray
var dataSet:NSMutableDictionary = NSMutableDictionary()
dataSet.setObject(textField.text, forKey: "exercises")
if ((itemList) != nil){
var newMutableList:NSMutableArray = NSMutableArray();
for dict:AnyObject in itemList!{
newMutableList.addObject(dict as NSDictionary)
}
userDefaults.removeObjectForKey("exercisesList")
newMutableList.addObject(dataSet)
userDefaults.setObject(newMutableList, forKey: "exercisesList")
}else{
userDefaults.removeObjectForKey("exercisesList")
itemList = NSMutableArray()
itemList!.addObject(dataSet)
userDefaults.setObject(itemList, forKey: "exercisesList")
}
userDefaults.synchronize()
self.view.endEditing(true)
textField.text = ""
}
ViewController1:
load data
var exercises:NSMutableArray = NSMutableArray();
override func viewDidAppear(animated: Bool) {
var userDefaults:NSUserDefaults = NSUserDefaults.standardUserDefaults()
var itemListFromUserDefaults:NSMutableArray? = userDefaults.objectForKey("itemList") as? NSMutableArray
if ((itemListFromUserDefaults) != nil){
exercises = itemListFromUserDefaults!
}
}
Now I wanted to use the loaded data for UIPickerView
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
switch pickerView {
case pickerView1:
return exercises[row] as String
case pickerView2:
return reps[component][row]
case pickerView3:
return weight[component][row]
default:
assertionFailure("Unknown pickerView")
}
}
But at the point where the UIPickerView should return the exercises it is empty. Need some help here.

I think I've got this.
In your code, you read exercises from UserDefaults in viewDidAppear, which will be called after func pickerView(...) -> String! is called. That's why in func pickerView(...) -> String! the exercises is always empty.
To do this correctly, you need to initialize exercises in init() or viewDidLoad()

Don't use NSUserDefaults to pass variables.
Add a public property (in the .h file) to destinationViewController and set that property with the value (the array) in prepareForSegue.
To save the array between app launches do as #Martin suggests, writing the array to the Documents directory.There are many answers here with that information.

How to store your arrays using NSUserDefaults is pretty simple and I encourage you to try it.
var exercises = ["A","B","C"]
NSUserDefaults.standardUserDefaults().setObject(exercises, forKey: "exercises")
How to load your string arrays using NSUserDefaults
if let myLoadedStringArray = NSUserDefaults.standardUserDefaults().stringArrayForKey("exercises"){
println(myLoadedStringArray) // "[A, B, C]"
}
// Note: myLoadedStringArray has no value outside curly brackets
How to delete/reset a value stored using NSUserDefaults
NSUserDefaults.standardUserDefaults().removeObjectForKey("exercises")
println(NSUserDefaults.standardUserDefaults().stringArrayForKey("exercises")) // "nil"
If you prefer to store it as a file at the documents folder you can do as follow
let fileUrl = (NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first! as NSURL).URLByAppendingPathComponent("exercises.db")
(exercises as NSArray).writeToURL(fileUrl, atomically: true) // true
// forced unwrap if you are sure it won't return nil
let loadedArray = NSArray(contentsOfURL: fileUrl)! as [String]
// or use if let to safely unwrap your array
if let loadedArray = NSArray(contentsOfURL: fileUrl) as? [String] {
println(loadedArray) // "[A, B, C]"
}

Related

use NSUserDefaults with array of struct type

I have problems to figure out how to save my string of type "RiskEntry" with NSUserDefaults. I already went through some other posts, but somehow I still did not manage to solve this particular issue.
Let me explain what the code from below does right now: I get some data from my class CustomCell in the following code snippet. Here I first check with an "identifier" which array to update with the new array value "consequences".
It all works fine and the updated array is stored in riskEntry.
However, I cannot work out how to store this with NSUserDefaults now. When I try it with e.g. riskItemDefaults.set(riskEntry, forKey: "riskItem") I get an exception error.
Any idea what I am doing wrong here?
SWIFT3 (I removed all code not relevant for this question)
class: RiskPlan: UIViewController, UITableViewDelegate, UITableViewDataSource, CustomCellUpdaterDelegate {
var riskEntry = [RiskEntry]()
var riskItemDefaults = UserDefaults.standard
// ------ start of delegate function (receiving from CustomCell) ---------
func transferData(consequencesTranferred: String, identifier: String) {
if let index = riskEntry.index(where: {$0.title as String == identifier}) {
riskEntry[index].consequences = consequencesTranferred
} else {
print ("nothing")
}
// save with NSUserDefaults
riskItemDefaults.set(riskEntry, forKey: "riskItem")
}
}
This is my struct:
public struct RiskEntry {
let title: String
var consequences: String
}
my Custom Cell
// ---------------- delegate to transfer entered data to VC -----------------
protocol CustomCellUpdaterDelegate {
func transferData(consequencesTranferred: String, identifier: String)
}
// ---------------- start of class CellCustomized -----------------
class CustomCell: UITableViewCell, UIPickerViewDataSource, UIPickerViewDelegate, UITextViewDelegate {
var delegate: CustomCellUpdaterDelegate?
// text fields, text views and picker views
#IBOutlet weak var riskTitle: UITextView!
#IBOutlet weak var consequences: UITextView!
// ---------------- listener for text view to save input in string when editing is finished -----------------
func textViewDidEndEditing(_ textView: UITextView) {
if textView.tag == 1 {
textConsequences = consequences.text
nameIdentifier = riskTitle.text
delegate?.transferData(consequencesTranferred: self.textConsequences, identifier: nameIdentifier)
} else {
print ("nothing")
}
}
}
The problem is you can't save your custom array in NSUserDefaults. To do that you should change them to NSData then save it in NSUserDefaults
Here is the code I used in my project it's in swift 2 syntax and I don't think it's going be hard to convert it to swift 3
let data = NSKeyedArchiver.archivedDataWithRootObject(yourObject);
NSUserDefaults.standardUserDefaults().setObject(data, forKey: "yourKey")
NSUserDefaults.standardUserDefaults().synchronize()
and to the get part use this combination
if let data = NSUserDefaults.standardUserDefaults().objectForKey("yourKey") as? NSData {
let myItem = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? yourType
}
hope this will help
Saving objects in UserDefaults have very specific restrictions:
set(_:forKey:) reference:
The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects, their contents must be property list objects.
You need to serialize your model, either using NSCoding or as an alternative using JSON, to map to a supported value by UserDefaults.
The closest type to a Swift struct that UserDefaults supports might be an NSDictionary. You could copy the struct elements into an Objective C NSDictionary object before saving the data.
I was able to program a solution based on #ahruss (How to save an array of custom struct to NSUserDefault with swift?). However, I modified it for swift 3 and it also shows how to implement this solution in a UITableView. I hope it can help someone in the future:
Add the extension from below to your structure (adjust it to your own variables)
Save the required array item like this:
let encoded = riskEntry.map { $0.encode() }
riskItemDefaults.set(encoded, forKey: "consequences")
riskItemDefaults.synchronize()
Load your item like this
let dataArray = riskItemDefaults.object(forKey: "consequences") as! [NSData]
let savedFoo = dataArray.map { RiskEntry(data: $0)! }
If you'd like to show the saved array item in your cells, proceed this way:
cell.consequences.text = savedFoo[indexPath.row].consequences as String
Here is the complete code, modified for Swift3
structure
// ---------------- structure for table row content -----------------
struct RiskEntry {
let title: String
var consequences: String
}
extension
extension RiskEntry {
init?(data: NSData) {
if let coding = NSKeyedUnarchiver.unarchiveObject(with: data as Data) as? Encoding {
title = coding.title as String
consequences = (coding.consequences as String?)!
} else {
return nil
}
}
func encode() -> NSData {
return NSKeyedArchiver.archivedData(withRootObject: Encoding(self)) as NSData
}
private class Encoding: NSObject, NSCoding {
let title : NSString
let consequences : NSString?
init(_ RiskEntry: RiskEntry) {
title = RiskEntry.title as NSString
consequences = RiskEntry.consequences as NSString?
}
public required init?(coder aDecoder: NSCoder) {
if let title = aDecoder.decodeObject(forKey: "title") as? NSString {
self.title = title
} else {
return nil
}
consequences = aDecoder.decodeObject(forKey: "consequences") as? NSString
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(title, forKey: "title")
aCoder.encode(consequences, forKey: "consequences")
}
}
}

NSUserDefaults Loading Issue

Whenever I open my app, it doesn't load my array values because the != nil function isn't called. Is there anything I can do about this?
Code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var toDoData = NSUserDefaults.standardUserDefaults()
if (toDoData.valueForKey("TDDATA") != nil){
todos = toDoData.valueForKey("TDDATA") as! NSArray as! [TodoModel]
}
if todos.count != 0{
toDoData.setValue(todos, forKeyPath: "TDDATA")
toDoData.synchronize()
}
}
Don't worry about the table. It populates perfectly. I just need the loading data issue fixed.
Code included in your answer helps a lot!
Thanks.
UPDATE:
Here is the TodoModel:
import Foundation
import UIKit
class TodoModel : NSObject, NSCoding {
var id: String
var image: String
var title: String
var desc: String
var scores: String
init (id: String, image: String, title: String, desc: String, scores: String) {
self.id = id
self.image = image
self.title = title
self.desc = desc
self.scores = scores
}
}
valueForKey and setValue:forKeyPath are KVC (Key Value Coding) methods (read here and here). It will not help you read/write to the user defaults database.
Looking in the NSUserDefaults documentation, there are a number of methods available for getting and setting values in the defaults database. Since you are using arrays, we will use:
arrayForKey to get.
setObject:forKey to set. (There is no array-specific setter)
EDIT: Try this in your viewDidAppear. Here we check if we have data, and if we do, we store it. If we don't have data, then check if the defaults database has some saved. If it does, use it instead. It would be advantageous to only load data from the defaults database in viewDidLoad, and then save in viewDidAppear or even better, a function which is called when a todo is added.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let defaults = NSUserDefaults.standardUserDefaults()
if todos.count > 0 {
// Save what we have
let data = NSKeyedArchiver.archivedDataWithRootObject(todos)
defaults.setObject(data, forKey: "TDDATA")
defaults.synchronize()
print("saved \(todos.count)")
} else if let storedTodoData = defaults.dataForKey("TDDATA"),
storedTodos = NSKeyedUnarchiver.unarchiveObjectWithData(storedTodoData) as? [TodoModel] {
// There was stored data! Use it!
todos = storedTodos
print("Used \(todos.count) stored todos")
}
}
In addition, we must implement the NSCoding protocol in your model. This should be something like this:
class TodoModel: NSObject, NSCoding {
var myInt: Int = 0
var myString: String?
var myArray: [String]?
required init?(coder aDecoder: NSCoder) {
myInt = aDecoder.decodeIntegerForKey("myInt")
myString = aDecoder.decodeObjectForKey("myString") as? String
myArray = aDecoder.decodeObjectForKey("myArray") as? [String]
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeInteger(myInt, forKey: "myInt")
aCoder.encodeObject(myString, forKey: "myString")
aCoder.encodeObject(myArray, forKey: "myArray")
}
}
(Of course, replace myInt, myString, myArray, etc, with whatever properties your model might have.)

NSKeyedUnarchiver won't return data

I am making an app that tracks a user's workouts. I have two custom classes, the first being ExerciseModel, which holds the data for each exercise performed during the workout, including the name, sets, reps, etc. Here is my data model:
import UIKit
class ExerciseModel: NSObject, NSCoding
{
// MARK: Properties
var name: String
var sets: Int
var reps: Int
var heartrate: Int?
var type: String?
//MARK: Archiving Paths
static let DocumentsDirectory = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
static let ArchiveURL = DocumentsDirectory.URLByAppendingPathComponent("exercises")
// MARK: Initialization
init?(name: String, sets: Int, reps: Int, heartrate: Int?, type: String)
{
// MARK: Initlaize stored properties
self.name = name
self.sets = sets
self.reps = reps
self.heartrate = heartrate
self.type = type
super.init()
// Initialization should fail if there is no name or sets is negative
if name.isEmpty || sets < 0
{
return nil
}
}
struct PropertyKey
{
static let nameKey = "name"
static let setKey = "sets"
static let repKey = "reps"
static let heartrateKey = "heartrate"
static let typekey = "type"
}
// MARK: NSCoding
func encodeWithCoder(aCoder: NSCoder)
{
aCoder.encodeObject(name, forKey: PropertyKey.nameKey)
aCoder.encodeInteger(sets, forKey: PropertyKey.setKey)
aCoder.encodeInteger(reps, forKey: PropertyKey.repKey)
aCoder.encodeObject(type, forKey: PropertyKey.typekey)
}
required convenience init?(coder aDecoder: NSCoder)
{
let name = aDecoder.decodeObjectForKey(PropertyKey.nameKey) as! String
let sets = aDecoder.decodeIntegerForKey(PropertyKey.setKey)
let reps = aDecoder.decodeIntegerForKey(PropertyKey.repKey)
let heartrate = aDecoder.decodeIntegerForKey(PropertyKey.heartrateKey)
let type = aDecoder.decodeObjectForKey(PropertyKey.typekey) as? String
// Must call designated initializer
self.init(name: name, sets: sets, reps: reps, heartrate: heartrate, type: type!)
}
init?(name: String, sets: Int, reps: Int, heartrate: Int, type: String)
{
// Initialize stored properties.
self.name = name
self.sets = sets
self.reps = reps
self.heartrate = heartrate
self.type = type
}
}
My second custom class is called WorkoutStorage, and this is meant to allow the user to save entire workouts and retrieve them later. The exercise property is an array of ExerciseModel objects, described above. Here is my data model for WorkoutStorage:
//
import UIKit
#objc(WorkoutStorage)
class WorkoutStorage: NSObject, NSCoding
{
// MARK: Properties
var name: String
var date: NSDate
var exercises: [ExerciseModel]
var maxHR: Int
var avgHR: Int
// MARK: Archiving Paths
static let DocumentsDirectory = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
static let ArchiveURL = DocumentsDirectory.URLByAppendingPathComponent("storedWorkouts")
// MARK: Initialization
init?(name: String, date: NSDate, exercises: [ExerciseModel], maxHR: Int, avgHR: Int)
{
//MARK: Initialize Stored Properties
self.name = name
self.date = date
self.exercises = exercises
self.maxHR = maxHR
self.avgHR = avgHR
super.init()
}
struct PropertyKey
{
static let nameKey = "name"
static let dateKey = "date"
static let exercisesKey = "exercises"
static let maxHRKey = "maxHR"
static let avgHRKey = "avgHR"
}
// MARK: NSCoding
func encodeWithCoder(aCoder: NSCoder)
{
aCoder.encodeObject(name, forKey: PropertyKey.nameKey)
aCoder.encodeObject(date, forKey: PropertyKey.dateKey)
aCoder.encodeObject(exercises, forKey: PropertyKey.exercisesKey)
aCoder.encodeInteger(maxHR, forKey: PropertyKey.maxHRKey)
aCoder.encodeInteger(avgHR, forKey: PropertyKey.avgHRKey)
}
required convenience init?(coder aDecoder: NSCoder)
{
let name = aDecoder.decodeObjectForKey(PropertyKey.nameKey) as! String
let date = aDecoder.decodeObjectForKey(PropertyKey.dateKey) as! NSDate
let exercises = aDecoder.decodeObjectForKey(PropertyKey.exercisesKey) as! [ExerciseModel]
let maxHR = aDecoder.decodeIntegerForKey(PropertyKey.maxHRKey)
let avgHR = aDecoder.decodeIntegerForKey(PropertyKey.avgHRKey)
// Must call designated initializer
self.init(name: name, date: date, exercises: exercises, maxHR: maxHR, avgHR: avgHR)
}
}
I followed the Apple tutorial for Persist Data to set up NSKeyedArchiver and NSKeyedUnarchiver for this, but I am still having trouble retrieving my data. When I try to load the Workouts, I call the following function:
func loadStoredWorkouts() -> WorkoutStorage
{
NSKeyedUnarchiver.setClass(WorkoutStorage.self, forClassName: "WorkoutStorage")
NSKeyedArchiver.setClassName("WorkoutStorage", forClass: WorkoutStorage.self)
print("\(WorkoutStorage.ArchiveURL.path!)")
return NSKeyedUnarchiver.unarchiveObjectWithFile(WorkoutStorage.ArchiveURL.path!) as! WorkoutStorage
}
Currently I can only return a single WorkoutStorage object, but when I attempt to retrieve an array containing all the stored WorkoutStorage objects, I get an error saying: Could not cast value of type 'Workout_Tracker.WorkoutStorage' (0x1000fcc80) to 'NSArray' (0x19f6b2418). I have read a lot of documentation trying to figure out why this will only return a single object, as well as checked out questions with similar issues, but to no avail. I originally set up my app following the Apple Persist Data tutorial to store and load my ExerciseModel objects, and that seems to work flawlessly. I set up the WorkoutStorage class the same way, but there seems to be an issue here.
Any help would be greatly appreciated!!
**Edit*
Here is the code I use to archive the WorkoutStorage object:
func saveWorkoutStorageObject(currentWorkout: WorkoutStorage)
{
NSKeyedUnarchiver.setClass(WorkoutStorage.self, forClassName: "WorkoutStorage")
NSKeyedArchiver.setClassName("WorkoutStorage", forClass: WorkoutStorage.self)
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(currentWorkout, toFile: WorkoutStorage.ArchiveURL.path!)
if !isSuccessfulSave
{
print("Failed to save exercises")
}
if isSuccessfulSave
{
print("Successful save of current workout: \(currentWorkout)")
}
}
Workouts are only created one at a time by the user, so each time one is completed, I pass the object to the above function to archive it.
To unarchive all the objects, I was trying to do something along the lines of:
var workouts = [WorkoutStorage]()
override func viewDidLoad()
{
super.viewDidLoad()
workouts = loadStoredWorkouts()
}
where the loadStoredWorkouts() function would be:
func loadStoredWorkouts() -> [WorkoutStorage]
{
NSKeyedUnarchiver.setClass(WorkoutStorage.self, forClassName: "WorkoutStorage")
NSKeyedArchiver.setClassName("WorkoutStorage", forClass: WorkoutStorage.self)
print("\(WorkoutStorage.ArchiveURL.path!)")
return NSKeyedUnarchiver.unarchiveObjectWithFile(WorkoutStorage.ArchiveURL.path!) as! [WorkoutStorage]
}
Your saveWorkoutStorageObject only archives a single workout. It doesn't archive the array, so of course you can't unarchive an array.
You need to archive the workouts array if you want to be able to unarchive an array.
Each time you archive something to a file you replace the contents of the file. It doesn't append to the end.
Since NSKeyedArchiver.archiveRootObject automatically archives child objects, all you need to do is archive the array and your WorkoutStorage objects will be archived automagically
func saveWorkouts(workouts:[WorkoutStorage])
{
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(workouts, toFile: WorkoutStorage.ArchiveURL.path!)
if isSuccessfulSave
{
print("Successful save of workouts: \(workouts)")
} else {
print("Failed to save exercises")
}
}

Adding object to enum gives me error

I have an enum, and a variable that points to a certain object in the enum.
enum Collection {
case First, Second, Third, Fourth
}
var myCollection = Collection.Second
I want to pass myCollection to the NSUserDefaults. Here's what I did: (I hope viewDidLoad is the right place to put it.)
override func viewDidLoad() {
super.viewDidLoad()
// Save the sort by NSUserDefualt
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(self.myCollection, forKey: "myKey")
}
At the last line, I get the following error:
Cannot invoke 'setObject' with an argument list of type '(myViewController.Collection, forKey: String)'
What am I doing wrong, and how can I fix it?
Update
Does this make sense?
let defaults = NSUserDefaults.standardUserDefaults()
let defaultRawValue = defaults.integerForKey("myKey")
if defaultRawValue != nil {
defaults.setInteger(myCollection.rawValue, forKey: "myKey")
} else {
defaults.setInteger(1, forKey: "myKey")
}
myCollection = Collection(rawValue: defaultRawValue)!
NSUserDefaults can only take PropertyList: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/PropertyLists/AboutPropertyLists/AboutPropertyLists.html
Try
enum Collection:Int{
case First=1, Second, Third, Fourth
}
var myCollection = Collection.Second
override func viewDidLoad() {
super.viewDidLoad()
let defaults = NSUserDefaults.standardUserDefaults()
let defaultRawValue = defaults.integerForKey("myKey")
if defaultRawValue > 0{
myCollection = Collection(rawValue: defaultRawValue)!
}
else
{
defaults.setInteger(1, forKey: "myKey")
myCollection = Collection(rawValue: 1)
}
}
The NSUserDefaults class provides convenience methods for accessing common types such as floats, doubles, integers, Booleans, and URLs. A default object must be a property list, that is, an instance of (or for collections a combination of instances of): NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary.
So, if you want to store your enum in NSUserDefaults, you use rawValue for your enum. Here I use String, like this:
enum Collection: String {
case First = "First"
case Second = "Second"
case Third = "Third"
case Fourth = "Fourth"
}
//create myCollection
var myCollection = Collection(rawValue: "Second")
And store it:
override func viewDidLoad() {
super.viewDidLoad()
// Save the sort by NSUserDefualt
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(self.myCollection.rawValue, forKey: "myKey")
}

Use data from NSUserDefaults in UIPickerView - pickerView stays empty

I tried to set up NSUserDefaults last night but an error keeps occurring:
ViewController3:
save data
#IBAction func tappedAddButton(sender: AnyObject) {
var userDefaults:NSUserDefaults = NSUserDefaults.standardUserDefaults()
var exercisesList:NSMutableArray? = userDefaults.objectForKey("exercisesList") as? NSMutableArray
var dataSet:NSMutableDictionary = NSMutableDictionary()
dataSet.setObject(textField.text, forKey: "exercises")
if ((exercisesList) != nil){
var newMutableList:NSMutableArray = NSMutableArray();
for dict:AnyObject in exercisesList!{
newMutableList.addObject(dict as NSDictionary)
}
userDefaults.removeObjectForKey("exercisesList")
newMutableList.addObject(dataSet)
userDefaults.setObject(newMutableList, forKey: "exercisesList")
}else{
userDefaults.removeObjectForKey("exercisesList")
exercisesList = NSMutableArray()
exercisesList!.addObject(dataSet)
userDefaults.setObject(exercisesList, forKey: "exercisesList")
}
userDefaults.synchronize()
self.view.endEditing(true)
textField.text = ""
}
ViewController1:
load data
var exercises:NSMutableArray = NSMutableArray();
...
override func viewDidAppear(animated: Bool) {
var userDefaults:NSUserDefaults = NSUserDefaults.standardUserDefaults()
var exercisesListFromUserDefaults:NSMutableArray? = userDefaults.objectForKey("exercisesList") as? NSMutableArray
if ((exercisesListFromUserDefaults) != nil){
exercises = exercisesListFromUserDefaults!
}
}
While adding some data to the variable "exercises", the pickerView stays empty.
You are setting userDefaults with the key "exercisesList" but attempting to get the data back with a different key ("itemList")

Resources