NSUserdefaults lost its memory - swift3

I am using Swift 3 and facebook login, and save some basic values i get from the facebookSDKGraphrequest in user defaults like this:
// save basic settings in standard user defaults: age, gender, first name
let defaults = UserDefaults.standard
defaults.set(firstName!, forKey: "firstName")
defaults.set(gender!, forKey: "gender")
let currentUserAge = calculateAge(birthday: birthDay!)
defaults.set(currentUserAge, forKey: "age")
I only need to run that code once when the user logs in, then I save the info into userDefaults, and I'm set.
so the issue i Have is that recently, the last few times I build and open my app on the phone, my userDefaults was empty.
when I run this code in another class,
let defaults = UserDefaults.standard
var name = defaults.object(forKey: "firstName") as? String
var age = defaults.integer(forKey: "age")
var gender = defaults.object(forKey: "gender") as? String
I was getting nil for all those values. So now I'm paranoid because if this happens in a user's app, it will crash.
Can someone explain why UserDefaults would lose its memory?
I did nothing to delete or reset the values. I went through probably 100 builds and this last time, UserDefaults' values were nil.

NSUserDefaults does not store immediately its content to disk. synchronize method is called periodically to write data to disk. It seems it is not called in your case. Try call defaults.synchronize when you have finished setting up your values.
(source : https://developer.apple.com/reference/foundation/userdefaults/1414005-synchronize)

Related

SwiftUI - TextField currency input as user types

So I'm using a TextField with a formatter, the issue is unless the user hits return that value is not captured which is causing some distress for users, even though I have added a label stating they should hit return, but in fairness it doesn't seem professional. How can I combat this issue? Is there a way in which I can capture the input as the user inputs data into the textfield, as well as formatting the input simultaneously?
This is my code:
var currencyFormatter: NumberFormatter = {
let val = NumberFormatter()
val.numberStyle = .currency
val.currencyCode = "GBP"
return val
}()
TextField("0", value: $viewModel.price, formatter: currencyFormatter)
.foregroundColor(Color(SYSTEM_FONT_COLOUR))
Thank you.

Retrieving the account name from Keychain

I want to save and retrieve the password and userAccount in keychain. I found a solution in stackoverflow and I use the code there. Save and Load from KeyChain | Swift. But this code only saves and load the password not the accountName. I think I figured out how to save the accountName but I need to figure out how to load it along with the password. Here is the code to save you some time from going into that link.
var userAccount = "AuthenticatedUser"
let accessGroup = "SecurityService"
// Mark: User defined keys for new entry
// Note: add new keys for new secure item and use them in load and save methods
let accountKey = "KeyForAccount"
let passwordKey = "KeyForPassword"
private class func save(service: String, data: String) {
let dataFromString: NSData = data.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue), allowLossyConversion: false)! as NSData
//Instantiate a new default keychain query
let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, dataFromString], forKeys: [kSecClassValue, kSecAttrServiceValue as NSCopying, kSecAttrAccountValue as NSCopying, kSecValueDataValue as NSCopying])
//Add new keychain item
let status = SecItemAdd(keychainQuery as CFDictionary, nil)
if status != errSecSuccess { //Always check status
print("Write failed. Attempting update.")
//updatePassword(token: data)
}
}
private class func load(service: String) -> String? {
//Instantiate a new default keychain query
//Tell the query to return a result
//Limit our results to one item
let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue as NSCopying, kSecAttrAccountValue as NSCopying, kSecReturnDataValue as NSCopying, kSecMatchLimitValue as NSCopying])
var dataTypeRef: AnyObject?
//Search for the keychain items
let status : OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)
var contentsOfKeychain: String? = nil
if status == errSecSuccess {
if let retrieveData = dataTypeRef as? Data {
contentsOfKeychain = String(data: retrieveData as Data, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue))
}
} else {
print("Nothing was retrieved from the keychain. Status code \(status)")
}
print(contentsOfKeychain ?? "none found")
return contentsOfKeychain
}
Complete code is inside the link. Thanks all.
But this code only saves and load the password not the accountName.
The code you show doesn't save or load the password or the account name.
What you have are two functions, one to save a key/value pair, one to load a key's value. It is how you call these two functions that determines what gets saved/loaded.
Somewhere in your code you call save to save the password, you need to call save (using a different key) to save the account name as well. Likewise for load. Your code does define the two keys to use (accountKey & passwordKey).
HTH

FMDB & Swift, " Optional("no such table: Student info") " in real device but it can be done in simulator

I'm newbie, plz help me to solve this out, I still have lots of other things to work on, really thank you thank you very much!
This is a further question after How to use FMDB on the generic iOS device instead of simulator?
When I execute the app on my device and the error threw out: "no such table: Student info", I've print all the path and they all pointed to the same file so I assumed the database has already copied? Console shows like this:
file:///var/mobile/Containers/Data/Application/B5E42F3C-524E-4BBF-8667-1EED0C963A77/Documents/
file:///var/mobile/Containers/Data/Application/B5E42F3C-524E-4BBF-8667-1EED0C963A77/Documents/Data.db
/var/mobile/Containers/Data/Application/B5E42F3C-524E-4BBF-8667-1EED0C963A77/Documents/Data.db
file:///var/mobile/Containers/Data/Application/B5E42F3C-524E-4BBF-8667-1EED0C963A77/Documents/
file:///var/mobile/Containers/Data/Application/B5E42F3C-524E-4BBF-8667-1EED0C963A77/Documents/Data.db
/var/mobile/Containers/Data/Application/B5E42F3C-524E-4BBF-8667-1EED0C963A77/Documents/Data.db
/var/mobile/Containers/Data/Application/B5E42F3C-524E-4BBF-8667-1EED0C963A77/Documents/Data.db
<NSFileManager: 0x17401c1b0>
2017-03-13 16:43:25.446039 Test1.3[16360:5045427] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
2017-03-13 16:43:25.457278 Test1.3[16360:5045427] [MC] Reading from public effective user settings.
Insert failed:
Optional("no such table: Student info")
The Data.db is in my bundle resources in target; and the contents in my device is a blank Data.db;
The 2nd question: If you look at the Utility.Swift in the previous question, although the app works good on simulator but after it was loaded, there should be an alertView said "Your database copy successfully", but it didn't. Following is that part of the code:
class func copyFile(_ fileName: NSString){
let dbPath: String = getPath(fileName as String)
let fileManager = FileManager.default
print(dbPath)
print(fileManager)
if !fileManager.fileExists(atPath: dbPath) {
let documentsURL = Bundle.main.resourceURL
let fromPath = documentsURL!.appendingPathComponent(fileName as String)
var error : NSError?
do {
try fileManager.copyItem(atPath: fromPath.path, toPath: dbPath)
}
catch let error1 as NSError {
error = error1
}
if(error != nil){
self.invokeAlertMethod("Error Occured", strBody: "\(error?.localizedDescription)" as NSString, delegate: nil)
}
else{
self.invokeAlertMethod("Successed", strBody: "Your database copy successfully", delegate: nil)
}
}
}
Okay for answering this question I went through your demo.
Where I found couple of mistakes. Let me go through one by one.
1) Your class Utility have a getPath method. What it does it will
keep copying db every time although db is already present in documents
directory and your documents directory db will be replaced with the sample structure. You should always check that if db is already present in documents directory or not.
2) Your db was getting copied into documents directory but structure
wasn't. There was no Student info table in db of documents directory.
3) Please avoid using space or any special characters in table names.
So what I did just corrected your method getPath in utility class.
Please replace your method with this one
class func getPath(_ fileName: String) -> String {
let bundlePath = Bundle.main.path(forResource: "Data", ofType: ".db")
let destPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let fileManager = FileManager.default
let fullDestPath = URL(fileURLWithPath: destPath).appendingPathComponent("Data.db")
if fileManager.fileExists(atPath: fullDestPath.path){
print("Database file is exist")
print(fileManager.fileExists(atPath: bundlePath!))
}else{
do{
try fileManager.copyItem(atPath: bundlePath!, toPath: fullDestPath.path)
}catch{
print("\n",error)
}
}
print(fullDestPath.path)
return fullDestPath.path
}
After changing this piece of code I tried to run in my device and inserted couple of records.
Let me know if you have any more questions.
If you find this answer helpful just accept it.
First trying delete your app and then reinstall it.
OR
I have created a project over FMDB in Swift which you can use to solve your issue. FMDB Wrapper class you can use in Objective C project as well.
https://github.com/LalitKY/Swift-FMDB
Hope this helps.

Google Script if statement does not check user vs entered data

I have a booking program in Google Sheets where a user can pick their name (uses data verification to provide a list of emails) and then the cell is then placed under protection so no other user can change the booking.
The strange thing that happens is that a person can enter in another person's email and then the cell is protected by the entered email not the user. The user can enter in a non-email string and it does not protect the cell.
The desired result would be that if the user's email and the data entered is the same, protect the cells otherwise it is free to be edited.
Any help would be appreciated!
function onEdit(e){
var CurrentUser = Session.getEffectiveUser().getEmail()
var range_DataEntered = SpreadsheetApp.getActiveRange();
var DataEntered = range_DataEntered.getValue();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var SheetName = SpreadsheetApp.getActiveSheet();
var Cell = ss.getActiveCell();
if (CurrentUser = DataEntered) {
var protection = range_DataEntered.protect()
// Ensure the current user is an editor before removing others. Otherwise, if the user's edit
// permission comes from a group, the script will throw an exception upon removing the group.
protection.addEditor(CurrentUser);
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
else {
//remove protection, set everyone to edit
range_DataEntered.protect().setDomainEdit(true);
}
}
if (CurrentUser = DataEntered) needs to be
if(CurrentUser === DataEntered)
A single = will assign a value not check for equivalency.

Trouble accessing child objects in Firebase

I'm trying to pull a list of connected users in Firebase to simply populate a select dropdown. My problem is that I can't seem to access the child objects properly.
Using connectedUsers.userName (see below code) works but only for my own user data, it doesn't pull anything else.
It seemed to me like changing "myUserRef.on" to "userListRef.on" and using something like "snapshot.child('userName').val()" should work but it just throws undefined. The same goes for "connectedUsers.child.userName", I'm sure I'm missing something simple here.
In the below code by changing to "userListRef.on('child_added', function(snapshot)" I can successfully add and remove user data from Firebase, and log all of the objects to the console and all data looks fine when I drill down the objects. I just need a way to access that data so I can put all connected users into a select dropdown or remove them from it when they disconnect.
var userListRef = new Firebase('https://myaccount.firebaseIO.com/users/');
var myUserRef = userListRef.push();
// ADD USER DATA TO FIREBASE
var userId = $('#myIdInput').val();
var userName = $('#nameInput').val();
myUserRef.push({userId: userId, userName: userName});
// READ USER OBJECTS AND FIRE ADDUSER FUNCTION
myUserRef.on('child_added', function(snapshot) {
var connectedUsers = snapshot.val();
console.log(addUser);
//addUser(connectedUsers.userId, connectedUsers.userName);
});
// ADD USER TO SELECT DROPDOWN
function addUser(userId, userName) {
var modSelect = $('#tsmmodsendto');
modSelect.append($('<option></option>').attr("value", userId).text(userName));
}
// READ USER OBJECTS AND FIRE REMOVEUSER FUNCTION
myUserRef.on('child_removed', function(snapshot) {
var connectedUsers = snapshot.val();
console.log(removeUser);
//removeUser(connectedUsers.userId, connectedUsers.userName);
});
// REMOVE USER TO SELECT DROPDOWN
function removeUser(userId, userName) {
var modSelect = $('#tsmmodsendto');
modSelect.append($('<option></option>').removeAttr("value", userId).text(userName));
}
// ON DISCONNECT REMOVE USER DATA FROM FIREBASE
myUserRef.onDisconnect().remove();
The code you've written won't work properly because each user is writing to:
/users/PUSH_ID/PUSH_ID
And is then listening at:
/users/PUSH_ID
So every client is going to be listening only to its own data and will never see anyone else's data. In order to view other people's data, you need to all be listening / writing to the same path.
In your question you mention you see "undefined" if you change to listening at /users. Could you simplify your code, and use that approach, and perhaps then I can provide a more helpful answer?
Or if I'm not understanding correctly, please simplify and clarify your question.