I'm trying to use the UserDefaults to persistently save a boolean value. This is my code :
public static var isOffline = UserDefaults.standard.bool(forKey: "isOffline") {
didSet {
print("Saving isOffline flag which is now \(isOffline)")
UserDefaults.standard.set(isOffline, forKey: "isOffline")
UserDefaults.standard.synchronize()
}
}
Why doesn't work? What is the problem in this code?
The problem is that when I try to retrieve "isOffline" key from UserDefaults I always get a false.
I set the isOffline in the .onChange method of the row (I'm using Eureka as framework to create forms). The flag keep the right value during the lifecycle of the app, but when I close it that value is probably removed in some way.
I had the same problem and the issue was in the "didSet" block itself. I don't know why, but it does not work with userDefaults - it does not persist it properly and after killing the application all changes were gone.
Synchronize() does not help. I found out, this method is no longer necessary and it will be deprecated in future (this is comment in UserDefaults class):
-synchronize is deprecated and will be marked with the NS_DEPRECATED macro in a future release.
By trial and error I found out, that it works, if I call it from main thread:
public static var isOffline = UserDefaults.standard.bool(forKey: "isOffline") {
didSet {
print("Saving isOffline flag which is now \(isOffline)")
DispatchQueue.main.async {
UserDefaults.standard.set(isOffline, forKey: "isOffline")
}
}
}
If anyone can explain, why it works on main thread and no other, I would be glad to hear it.
try to change
UserDefaults.standard.set(isOffline, forKey: "isOffline")
to
UserDefaults.standard.setValue(isOffline, forKey: "isOffline")
without the dispatch code
Do Like this,
public static var isOffline:Bool {
get {
return UserDefaults.standard.bool(forKey: "isOffline")
}
set(newValue) {
print("Saving isOffline flag which is now \(isOffline)")
UserDefaults.standard.set(newValue, forKey: "isOffline")
UserDefaults.standard.synchronize()
}
}
Don't ask me why, but I had this issue also in a project in Xcode 14.
After hours of debugging I realized that the length of my key exceeded 18 characters, it would no longer be saved. However, I couldn't reproduce this in a vanilla project, so I guess it had something to do with the project.
But anyway: if you're experiencing this, try decreasing the character count of your key.
Related
I'm struggling to debug a hard-to-reproduce-locally crash in my app. The crash reports show the exception type is EXC_BAD_ACCESS (SIGSEGV) with objc_msgSend at the top of the crashed thread, perhaps suggesting "The process may have attempted to message a deallocated object" (as per Apple's documents).
I have not been able to recreate the crash or find any zombies using the Zombies instrument, despite using an older device and the same simulated device causing the most crashes.
From the trace I'm pretty sure I've been able to work out where the crash is occurring, but I'm not sure what I'm doing wrong.
My app is basically a group database. The app takes a username and password, sending them to a server to check if the person can be logged in. If valid, the app downloads the data on all the people in the group, then finds the logged in person from the downloaded data based on their username.
Because of the way the server code is set up, the username must be present in the downloaded data (if it all downloads correctly), yet my code occasionally fails to find them, and (from the crash logs) also occasionally this attempted finding of the person causes a crash.
coreDataStack.storeContainer.performBackgroundTask() { context in
OverallNetworkCalls.deleteAllLoadPeople(moContext: context) { result in
self.coreDataStack.saveNewContext(context: context)
if result == .success {
let person = AdministrativeHelperFunctions.returnCurrentUserFromUserName(moContext: context)
if let person = person {
DispatchQueue.main.async {
self.activityIndicator.stopAnimating()
}
UserDefaults.standard.set(person.objectID.uriRepresentation(), forKey: udl.CurrentUserMOID.rawValue)
} else {
DispatchQueue.main.async {
self.activityIndicator.stopAnimating()
self.loadingLabel.text = "Data was downloaded but your username isn't there. Try deleting and reloading the app, or contact admin."
}
}
}
func saveNewContext(context: NSManagedObjectContext) {
context.perform {
guard context.hasChanges else { return }
do {
try context.save()
} catch {
//logs the issue locally
}
}
}
func returnCurrentUserFromUserName(moContext: NSManagedObjectContext) -> Person? {
let userName = UserDefaults.standard.string(forKey: "username")
if let userName = userName {
//Create predicate, arrayForResults etc
do {
userNameMatchesArray = try moContext.fetch(fetchRequest)
} catch {
//logs the issue locally
}
if userNameMatchesArray.count == 1 {
return userNameMatchesArray[0]
} else {
return nil
}
} else {
return nil
}
}
My suspicion is that the returnCurrentUserFromUserName function is returning the user before the saveNewContext function has completed its task and that this is contributing to the issue, though given the same managedObjectContext is saving then retrieving the person I had originally assumed this wasn't a problem. I'm also not sure that using "context.perform" in the saveNewContext function is necessary/wise/useful; I'd value feedback on this too if anyone knows best practice inside a performBackgroundTask block.
A non-reproduceable bug is always the most frustrating to fix, and because of this anything I try to fix it won't be shown until I send it to my beta testers and get crashlogs back (or not!).
Thanks in advance for reviewing this problem.
I "inherited" an iOS Xcode Project in Swift. I've never programmed in Swift or used Xcode before. I copied the project from one user account to another and checked it into a git repo.
Now I'm araid this (the copying, permissions ?) might be the cause for the App not being able to read its settings properly. Because UserDefaults.standard.string(forKey: "pref_Foo") returns nil although in the Settings.Bundle's Root.plist there's clearly a pref_Foo identifier.
The App has worked before, so I don't see where this suddenly comes from.
Since I'm not too familiar with XCode all I did up til now was debug into the Application to see that UserDefaults.standard.string(forKey: "pref_Foo") is nil.
How could I approach this problem?
Thank you!
EDIT: This is part of my code in ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
loadSettings()
...
}
func loadSettings()
{
if(UserDefaults.standard.object(forKey: "pref_Foo") == nil)
{
printf("Error")
}
}
You should use below code with validation, first delete app and install again. then use, you will get success.
var UserStatus:String {
get{
return UserDefaults.standard.object(forKey: "pref_Foo") as? String ?? "0"
}
set(status){
UserDefaults.standard.set(status, forKey: "pref_Foo")
UserDefaults.standard.synchronize()
}
}
Use of code is as below.
if (UserStatus == "pref_Foo"){
self.logoutSuccess()
} else {
self.loginSuccess()
}
Update or set value is so simple in one line code as given below.
UserStatus = "pref_Foo"
I'm using Firebase listener to update state values for a remote camera. Once I have cycled through the camera lifecycle I want to remove the listeners so my camera does not start over and continue to take video.
Here is what I've done so far based on SO suggestions:
1) added FIRDatabaseHandle and called removeObserver(withHandle: handle) / no luck
2) simple called removeAllObservers() from the root reference to what you see below.
struct CameraActions {
let db = DataService.ds.db // this comes from a singleton used to for other Firebase calls
let uid = DataService.ds.curUser?.uid
var cameraRef:FIRDatabaseReference!
mutating func addCameraListener(cameraNum num:String, complete:#escaping(CameraStatus)->Void){
cameraRef = db.child("camera").child(num).child("status")
cameraRef.observe(.value, with: {
snap in
if let status = snap.value as? Int {
switch status {
case 0: complete(.ready)
case 2: complete(.isRecording)
case 4: complete(.hasStopped)
case 5: complete(.problem)
default: print("App is waiting on camera")
}
}
})
}
func cameraHasFinishedRecording(cameraNum num: String) {
cameraRef.removeAllObservers() // latest attempt here
db.child("camera").child(num).child("status").setValue(0) // this still triggers database call
}
Thanks in advance for any assistance.
Firebase works exactly as advertised. The removal of the observer was working but, another observer that should've been a single observer was firing. Thanks for the input and sorry for wasting your time.
Cheers!
I'm using Fastlane's snapshot to create screenshots for an app I'm about to submit to the App Store.
It works "as advertised" for the most part, but it doesn't seem to like the way I access the UserDefaults within my app. On one test, it generates an Exit status: 65 error.
UI Testing Failure - com.me.MyApp crashed in (extension in MyApp):__ObjC.NSObject.defaultTime () -> Swift.Float
I find UserDefaults.standard.value(forKey: "defaultTime") to an invitation for a syntax error, so I created an extension to access UserDefaults. Here's what the extension looks like:
class CustomExtensions: NSObject {
/*
This is blank. Nothing else in here. No really...nothing else
*/
}
extension NSObject {
// User Defaults
func defaultTime() -> Float {
return UserDefaults.standard.value(forKey: "defaultTime") as! Float
}
// a bunch of other UserDefaults
}
Wihin the app, whenever I need defaultTime, I just type defaultTime(). Using this method to access UserDefaults values works fine in the Simulator and on the devices I've tested. I only encounter a problem with snapshot.
I've tried adding in sleep(1) within the test, but that doesn't seem to do anything. I welcome suggestions re: alternative means of accessing UserDefaults that enable me to access them easily throughout MyApp.
What's probably happening is that, in your simulator and on device, you're writing a value to user defaults for the key defaultTime before it is ever read. value(forKey: returns an optional, and if you force-unwrap it (or force down-cast as your are doing here), you will crash if the value is nil. Try either returning an optional:
func defaultTime() -> Float? {
return UserDefaults.standard.value(forKey: "defaultTime") as? Float
}
or using a default value:
func defaultTime() -> Float {
return UserDefaults.standard.value(forKey: "defaultTime") as? Float ?? 0.0
}
In swift I am making a app where it asks for a code before going to the actual app. I only want this to be showed once. How do I have this page only showed the first time then they enter the code and they only have to do this once then when they use the app again they don't need to enter a code. Thanks.
Your best shot is NSUserDefaults. Save a flag (a boolean) in NSUserDefaults to indicate whether it is the first time the user has opened the app.
let userDefaults = NSUserDefaults.standardUserDefaults()
if userDefaults.valueForKey("IS_FIRST_TIME") as? Bool != nil {
// First time, do something...
// ...
// ...
// Now, save a flag to indicate that this was the first time.
userDefaults.setValue(true, forKey: "IS_FIRST_TIME")
userDefaults.synchronize() // Needed to save the new value.
}